1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2008 - 2021, Phoronix Media
7	Copyright (C) 2008 - 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_client
24{
25	public static $display = false;
26	public static $pts_logger = false;
27	public static $web_result_viewer_active = false;
28	public static $web_result_viewer_access_key = false;
29	public static $has_used_modern_result_viewer = false;
30	public static $last_browser_launch_time = 0;
31	public static $last_browser_duration = 0;
32	public static $last_result_view_url = null;
33	public static $skip_log_file_type_checks = false;
34	protected static $lock_pointers = null;
35	protected static $phoromatic_servers = array();
36	protected static $debug_mode = false;
37	protected static $full_output = false;
38	protected static $override_pts_env_vars = array();
39	protected static $sent_command = null;
40	protected static $time_pts_last_launch = null;
41	private static $current_command = null;
42	private static $forked_pids = array();
43	private static $download_speed_average_count = -1;
44	private static $download_speed_average_speed = -1;
45
46	public static function create_lock($lock_file)
47	{
48		if(isset(self::$lock_pointers[$lock_file]) || is_writable(dirname($lock_file)) == false)
49		{
50			return false;
51		}
52		else if(disk_free_space(dirname($lock_file)) < 1024)
53		{
54			echo PHP_EOL . 'Lock creation failed due to lack of disk space.' . PHP_EOL;
55			return false;
56		}
57
58		self::$lock_pointers[$lock_file] = fopen($lock_file, 'w');
59		chmod($lock_file, 0644);
60		return self::$lock_pointers[$lock_file] != false && (phodevi::is_windows() || flock(self::$lock_pointers[$lock_file], LOCK_EX | LOCK_NB));
61	}
62	public static function current_time()
63	{
64		$current_time = time();
65
66		// Calculate time based on offset from last OpenBenchmarking.org check and then the time since that file was modified
67		$repo_index = pts_openbenchmarking::read_repository_index('pts');
68		if($repo_index && isset($repo_index['main']['generated']))
69		{
70			$repo_time = $repo_index['main']['generated'];
71			if(($index = pts_openbenchmarking::is_repository('pts')))
72			{
73				$last_modified = filemtime($index);
74				$time_since_modify = time() - $last_modified;
75			}
76
77			$calculated_time = $repo_time + $time_since_modify;
78			if($calculated_time > $current_time)
79			{
80				$current_time = $calculated_time;
81			}
82		}
83
84		// Fallback to checking time since the PTS release to see if date computed is behind that
85		if(($fallback_time = strtotime(PTS_RELEASE_DATE . ' ' . date('H:i:s'))) > $current_time)
86		{
87			$current_time = $fallback_time;
88		}
89
90		return $current_time;
91	}
92	public static function possible_sub_commands()
93	{
94		static $options = null;
95
96		if(empty($options))
97		{
98			$options = array();
99			foreach(pts_file_io::glob(PTS_COMMAND_PATH . '*.php') as $option_php)
100			{
101				$name = str_replace('_', '-', basename($option_php, '.php'));
102				if(!in_array(pts_strings::first_in_string($name, '-'), array('dump', 'debug', 'task')))
103				{
104					$options[] = $name;
105				}
106			}
107		}
108
109		return $options;
110	}
111	public static function is_locked($lock_file)
112	{
113		$fp = fopen($lock_file, 'w');
114		$is_locked = $fp && !flock($fp, LOCK_EX | LOCK_NB);
115		$fp && fclose($fp);
116
117		return $is_locked;
118	}
119	public static function release_lock($lock_file)
120	{
121		// Remove lock
122		if(isset(self::$lock_pointers[$lock_file]) == false)
123		{
124			pts_file_io::unlink($lock_file);
125			return false;
126		}
127
128		if(is_resource(self::$lock_pointers[$lock_file]))
129		{
130			fclose(self::$lock_pointers[$lock_file]);
131		}
132
133		pts_file_io::unlink($lock_file);
134		unset(self::$lock_pointers[$lock_file]);
135	}
136	public static function write_to_lock($lock_file, $contents)
137	{
138		if(isset(self::$lock_pointers[$lock_file]) && is_resource(self::$lock_pointers[$lock_file]))
139		{
140			fwrite(self::$lock_pointers[$lock_file], $contents);
141			fflush(self::$lock_pointers[$lock_file]);
142		}
143	}
144	public static function init()
145	{
146		pts_core::init();
147		pts_define('PTS_COMMAND_PATH', PTS_CORE_PATH . 'commands/');
148
149		if(defined('QUICK_START') && QUICK_START)
150		{
151			return true;
152		}
153
154
155		if(function_exists('cli_set_process_title') && PHP_OS == 'Linux')
156		{
157			cli_set_process_title('Phoronix Test Suite');
158		}
159
160		pts_define('PHP_BIN', pts_client::read_env('PHP_BIN'));
161
162		$dir_init = array(PTS_USER_PATH);
163		foreach($dir_init as $dir)
164		{
165			pts_file_io::mkdir($dir);
166		}
167
168		if(PTS_IS_CLIENT)
169		{
170			pts_network::client_startup();
171		}
172
173		self::core_storage_init_process();
174		$p = pts_config::read_path_config('PhoronixTestSuite/Options/Installation/EnvironmentDirectory', '~/.phoronix-test-suite/installed-tests/');
175		if(phodevi::is_windows())
176		{
177			$p = str_replace('/', DIRECTORY_SEPARATOR, $p);
178		}
179		pts_define('PTS_TEST_INSTALL_DEFAULT_PATH', $p);
180
181		pts_define('PTS_SAVE_RESULTS_PATH', pts_config::read_path_config('PhoronixTestSuite/Options/Testing/ResultsDirectory', '~/.phoronix-test-suite/test-results/'));
182		self::extended_init_process();
183
184		$openbenchmarking = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'openbenchmarking');
185		$openbenchmarking_account_settings = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'openbenchmarking_account_settings');
186
187		if($openbenchmarking != null)
188		{
189			// OpenBenchmarking.org Account
190			pts_openbenchmarking_client::init_account($openbenchmarking, $openbenchmarking_account_settings);
191		}
192
193		return true;
194	}
195	private static function extended_init_process()
196	{
197		// Extended Initalization Process
198		$directory_check = array(
199			PTS_TEST_INSTALL_DEFAULT_PATH,
200			PTS_SAVE_RESULTS_PATH,
201			pts_module::module_local_path(),
202			pts_module::module_data_path(),
203			PTS_DOWNLOAD_CACHE_PATH,
204			PTS_OPENBENCHMARKING_SCRATCH_PATH,
205			PTS_TEST_PROFILE_PATH,
206			PTS_TEST_SUITE_PATH,
207			PTS_TEST_PROFILE_PATH . 'local/',
208			PTS_TEST_SUITE_PATH . 'local/'
209			);
210
211		$tp_pts_dir_not = is_dir(PTS_TEST_PROFILE_PATH . 'pts/');
212
213		foreach($directory_check as $dir)
214		{
215			pts_file_io::mkdir($dir);
216		}
217
218		// Copy files (without overwrite) from internal OB program cache if present, to help those without Internet
219		if(!phodevi::is_windows() && (FIRST_RUN_ON_PTS_UPGRADE || !$tp_pts_dir_not))
220		{
221			if(PTS_INTERNAL_OB_CACHE && is_dir(PTS_INTERNAL_OB_CACHE . 'test-profiles'))
222			{
223				pts_file_io::copy(PTS_INTERNAL_OB_CACHE . 'test-profiles', PTS_TEST_PROFILE_PATH, true);
224			}
225			if(PTS_INTERNAL_OB_CACHE && is_dir(PTS_INTERNAL_OB_CACHE . 'test-suites'))
226			{
227				pts_file_io::copy(PTS_INTERNAL_OB_CACHE . 'test-suites', PTS_TEST_SUITE_PATH, true);
228			}
229			if(PTS_INTERNAL_OB_CACHE && is_dir(PTS_INTERNAL_OB_CACHE . 'openbenchmarking.org'))
230			{
231				pts_file_io::copy(PTS_INTERNAL_OB_CACHE . 'openbenchmarking.org', PTS_OPENBENCHMARKING_SCRATCH_PATH, true);
232
233				if(!pts_network::internet_support_available())
234				{
235					// Only overwrite the OB index files if it's newer
236					foreach(pts_file_io::glob(PTS_INTERNAL_OB_CACHE . 'openbenchmarking.org/*.index') as $cache_index_file)
237					{
238						$index_file_name = basename($cache_index_file);
239						if(is_file(PTS_OPENBENCHMARKING_SCRATCH_PATH . $index_file_name))
240						{
241							$current_version = pts_openbenchmarking::get_generated_time_from_index(PTS_OPENBENCHMARKING_SCRATCH_PATH . $index_file_name);
242							$cached_version = pts_openbenchmarking::get_generated_time_from_index($cache_index_file);
243							if($cached_version > $current_version)
244							{
245								copy($cache_index_file, PTS_OPENBENCHMARKING_SCRATCH_PATH . $index_file_name);
246							}
247						}
248					}
249				}
250			}
251		}
252
253		// pts_compatibility ops here
254
255		pts_client::init_display_mode();
256	}
257	public static function module_framework_init()
258	{
259		// Process initially called when PTS starts up
260		// Check for modules to auto-load from the configuration file
261		$load_modules = pts_config::read_user_config('PhoronixTestSuite/Options/Modules/AutoLoadModules', null);
262
263		if(!empty($load_modules))
264		{
265			foreach(pts_strings::comma_explode($load_modules) as $module)
266			{
267				$module_r = pts_strings::trim_explode('=', $module);
268
269				if(count($module_r) == 2)
270				{
271					// Ideally end up hooking this into pts_module::read_variable() rather than using the real env
272					pts_client::pts_set_environment_variable($module_r[0], $module_r[1]);
273				}
274				else
275				{
276					pts_module_manager::attach_module($module);
277				}
278			}
279		}
280
281		// Check for modules to load manually in PTS_MODULES
282		if(($load_modules = pts_client::read_env('PTS_MODULES')) !== false)
283		{
284			foreach(pts_strings::comma_explode($load_modules) as $module)
285			{
286				if(!pts_module_manager::is_module_attached($module))
287				{
288					pts_module_manager::attach_module($module);
289				}
290			}
291		}
292
293		// Detect modules to load automatically
294		pts_module_manager::detect_modules_to_load();
295
296		// Clean-up modules list
297		pts_module_manager::clean_module_list();
298
299		// Reset counter
300		pts_module_manager::set_current_module(null);
301
302		// Load the modules
303		$module_store_list = array();
304		foreach(pts_module_manager::attached_modules() as $module)
305		{
306			$class_vars = get_class_vars($module);
307			$module_store_vars = isset($class_vars['module_store_vars']) ? $class_vars['module_store_vars'] : null;
308
309			if(is_array($module_store_vars))
310			{
311				foreach($module_store_vars as $store_var)
312				{
313					if(!in_array($store_var, $module_store_list))
314					{
315						$module_store_list[] = $store_var;
316					}
317				}
318			}
319		}
320
321		// Should any of the module options be saved to the results?
322		foreach($module_store_list as $var)
323		{
324			$var_value = pts_client::read_env($var);
325
326			if(!empty($var_value))
327			{
328				pts_module_manager::var_store_add($var, $var_value);
329			}
330		}
331
332		pts_module_manager::module_process('__startup');
333		pts_define('PTS_STARTUP_TASK_PERFORMED', true);
334		register_shutdown_function(array('pts_module_manager', 'module_process'), '__shutdown');
335	}
336	public static function environmental_variables()
337	{
338		// The PTS environmental variables passed during the testing process, etc
339		static $env_variables = null;
340
341		if($env_variables == null)
342		{
343			$physical_cores = phodevi::read_property('cpu', 'physical-core-count');
344			$i = 0;
345			do
346			{
347				$i++;
348				$nearest_cube = pow($i, 3);
349			}
350			while($physical_cores >= pow(($i + 1), 3));
351
352			$tpc = phodevi::read_property('cpu', 'core-count') / $physical_cores;
353			if($tpc < 1 || !is_integer($tpc))
354			{
355				$tpc = 1;
356			}
357
358			$env_variables = array(
359			'PTS_VERSION' => PTS_VERSION,
360			'PTS_CODENAME' => PTS_CODENAME,
361			'PTS_DIR' => PTS_PATH,
362			'PTS_LAUNCHER' => getenv('PTS_LAUNCHER'),
363			'PHP_BIN' => PHP_BIN,
364			'NUM_CPU_CORES' => phodevi::read_property('cpu', 'core-count'),
365			'OMP_NUM_THREADS' => phodevi::read_property('cpu', 'core-count'),
366			'NUM_CPU_PHYSICAL_CORES' => $physical_cores,
367			'NUM_CPU_PHYSICAL_CORES_CUBE' => $nearest_cube,
368			'CPU_THREADS_PER_CORE' => $tpc,
369			'NUM_CPU_NODES' => phodevi::read_property('cpu', 'node-count'),
370			'NUM_CPU_JOBS' => (phodevi::read_property('cpu', 'core-count') * 2),
371			'SYS_MEMORY' => phodevi::read_property('memory', 'capacity'),
372			'VIDEO_MEMORY' => phodevi::read_property('gpu', 'memory-capacity'),
373			'VIDEO_WIDTH' => pts_arrays::first_element(phodevi::read_property('gpu', 'screen-resolution')),
374			'VIDEO_HEIGHT' => pts_arrays::last_element(phodevi::read_property('gpu', 'screen-resolution')),
375			'VIDEO_MONITOR_COUNT' => phodevi::read_property('monitor', 'count'),
376			'VIDEO_MONITOR_LAYOUT' => phodevi::read_property('monitor', 'layout'),
377			'VIDEO_MONITOR_SIZES' => phodevi::read_property('monitor', 'modes'),
378			'OPERATING_SYSTEM' => phodevi::read_property('system', 'vendor-identifier'),
379			'OS_VERSION' => phodevi::read_property('system', 'os-version'),
380			'OS_ARCH' => phodevi::read_property('system', 'kernel-architecture'),
381			'OS_TYPE' => phodevi::os_under_test(),
382			'CPU_FAMILY' => str_replace(' ', '', strtolower(phodevi::read_property('cpu', 'core-family-name'))),
383			'THIS_RUN_TIME' => PTS_INIT_TIME,
384			'DEBUG_REAL_HOME' => pts_core::user_home_directory(),
385			'DEBUG_PATH' => pts_client::get_path(),
386			'SYSTEM_TYPE_ID' => phodevi_base::determine_system_type(phodevi::system_hardware(), phodevi::system_software()),
387			'SYSTEM_TYPE' => phodevi_base::system_type_to_string(phodevi_base::determine_system_type(phodevi::system_hardware(), phodevi::system_software())),
388			'TERMINAL_WIDTH' => pts_client::terminal_width(),
389			'C_CXX_FLAGS_DEFAULT' => '-O3 -march=native', // mostly for future use
390			'GPU_DEVICE_ID' => phodevi::read_property('gpu', 'device-id'),
391			//'PATH' => pts_client::get_path()
392			);
393
394			if(!pts_client::executable_in_path('cc') && getenv('CC') == false)
395			{
396				// This helps some test profiles build correctly if they don't do a cc check internally
397				if(pts_client::executable_in_path('gcc'))
398				{
399					$env_variables['CC'] = 'gcc';
400				}
401				else if(pts_client::executable_in_path('clang'))
402				{
403					$env_variables['CC'] = 'clang';
404				}
405			}
406		}
407
408		return array_merge($env_variables, self::$override_pts_env_vars);
409	}
410	public static function override_pts_env_var($name, $value)
411	{
412		self::$override_pts_env_vars[$name] = $value;
413	}
414	public static function unset_pts_env_var_override($name)
415	{
416		if(isset(self::$override_pts_env_vars[$name]))
417		{
418			unset(self::$override_pts_env_vars[$name]);
419		}
420	}
421	public static function test_install_root_path()
422	{
423		if(getenv('PTS_TEST_INSTALL_ROOT_PATH') != false && is_dir(getenv('PTS_TEST_INSTALL_ROOT_PATH')) && is_writable(getenv('PTS_TEST_INSTALL_ROOT_PATH')))
424		{
425			return getenv('PTS_TEST_INSTALL_ROOT_PATH');
426		}
427		else
428		{
429			if(!defined('PTS_TEST_INSTALL_DEFAULT_PATH'))
430			{
431				$p = pts_config::read_path_config('PhoronixTestSuite/Options/Installation/EnvironmentDirectory', '~/.phoronix-test-suite/installed-tests/');
432				if(phodevi::is_windows())
433				{
434					$p = str_replace('/', DIRECTORY_SEPARATOR, $p);
435				}
436
437				pts_define('PTS_TEST_INSTALL_DEFAULT_PATH', $p);
438			}
439
440			return PTS_TEST_INSTALL_DEFAULT_PATH;
441		}
442	}
443	public static function download_cache_path()
444	{
445		return pts_strings::add_trailing_slash(pts_config::read_path_config('PhoronixTestSuite/Options/Installation/CacheDirectory', PTS_DOWNLOAD_CACHE_PATH));
446	}
447	public static function supports_colored_text_output()
448	{
449		static $supports = -1;
450
451		if($supports == -1)
452		{
453			if(getenv('NO_COLOR'))
454			{
455				$supported = false;
456			}
457			else
458			{
459				$config_color_option = pts_config::read_user_config('PhoronixTestSuite/Options/General/ColoredConsole', 'AUTO');
460
461				switch(strtoupper($config_color_option))
462				{
463					case 'TRUE':
464						$supported = true;
465						break;
466					case 'FALSE':
467						$supported = false;
468						break;
469					case 'AUTO':
470					default:
471						$supported = (function_exists('posix_isatty') && posix_isatty(STDOUT)) || (PTS_IS_CLIENT && (getenv('LS_COLORS') || getenv('CLICOLOR'))) || (phodevi::is_windows() && strstr(phodevi::read_property('system', 'operating-system'), 'Windows 8') === false && strstr(phodevi::read_property('system', 'operating-system'), 'Windows 7') === false);
472						break;
473				}
474			}
475			$supports = $supported;
476		}
477
478		return $supports;
479	}
480	public static function hex_color_to_string($hex)
481	{
482		$colors = array();
483		list($colors['red'], $colors['green'], $colors['blue']) = sscanf($hex, "#%02x%02x%02x");
484		if($colors['red'] > 240 && $colors['green'] > 210)
485		{
486			return 'yellow'; // this works for Arm color, etc
487		}
488		return array_search(max($colors), $colors);
489	}
490	public static function cli_colored_text($str, $color, $bold = false)
491	{
492		if(!self::supports_colored_text_output() || empty($color))
493		{
494			return $str;
495		}
496
497		$attribute = ($bold ? '1' : '0');
498		$colors = array(
499			'black' => $attribute . ';30',
500			'gray' => '1;30', // gray not bold doesn't look good in all consoles
501			'blue' => $attribute . ';34',
502			'magenta' => $attribute . ';35',
503			'green' => $attribute . ';32',
504			'yellow' => $attribute . ';33',
505			'red' => $attribute . ';31',
506			'cyan' => $attribute . ';36',
507			'white' => $attribute . ';37',
508			);
509
510		if(!isset($colors[$color]))
511		{
512			return $str;
513		}
514
515		return "\033[" . $colors[$color] . 'm' . $str . "\033[0m";
516	}
517	public static function cli_just_bold($str)
518	{
519		if(!self::supports_colored_text_output())
520		{
521			return $str;
522		}
523
524		return "\033[1m$str\033[0m";
525	}
526	public static function cli_just_italic($str)
527	{
528		if(!self::supports_colored_text_output())
529		{
530			return $str;
531		}
532
533		return "\e[3m$str\e[0m";
534	}
535	public static function cli_just_underline($str)
536	{
537		if(!self::supports_colored_text_output())
538		{
539			return $str;
540		}
541
542		return "\e[4m$str\e[0m";
543	}
544	public static function save_test_result($save_to = null, $save_results = null, $render_graphs = true, $result_identifier = null)
545	{
546		// Saves PTS result file
547		if(substr($save_to, -4) != '.xml')
548		{
549			$save_to .= '.xml';
550		}
551		$save_to = str_replace(PTS_SAVE_RESULTS_PATH, '', $save_to);
552
553		$save_to_dir = pts_client::setup_test_result_directory($save_to);
554
555		if($save_to == null || $save_results == null)
556		{
557			$bool = false;
558		}
559		else
560		{
561			$save_name = basename($save_to, '.xml');
562
563			$bool = file_put_contents(PTS_SAVE_RESULTS_PATH . $save_to, $save_results);
564
565			if($result_identifier != null && pts_config::read_bool_config('PhoronixTestSuite/Options/Testing/SaveSystemLogs', 'TRUE'))
566			{
567				// Save verbose system information here
568				//$system_log_dir = $result_file->get_system_log_dir($result_identifier, false);
569				$system_log_dir = $save_to_dir . '/system-logs/' . pts_strings::simplify_string_for_file_handling($result_identifier) . '/';
570				pts_file_io::mkdir($system_log_dir, 0777, true);
571
572				// Backup system files
573				// TODO: move out these files/commands to log out to respective Phodevi components so only what's relevant will be logged
574				$system_log_files = array(
575					'/var/log/Xorg.0.log',
576					'/proc/cpuinfo',
577					'/proc/meminfo',
578					'/proc/modules',
579					'/proc/mounts',
580					'/proc/cmdline',
581					'/proc/version',
582					'/proc/mdstat',
583					'/proc/lock_stat',
584					'/etc/X11/xorg.conf',
585					'/sys/kernel/debug/dri/0/radeon_pm_info',
586					'/sys/kernel/debug/dri/0/i915_capabilities',
587					'/sys/kernel/debug/dri/0/i915_cur_delayinfo',
588					'/sys/kernel/debug/dri/0/i915_drpc_info',
589					'/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies',
590					);
591
592				/*
593				if(phodevi::is_linux())
594				{
595					// the kernel config file might just be too large to upload for now
596					$system_log_files[] = '/boot/config-' . php_uname('r');
597				}
598				*/
599
600				foreach($system_log_files as $file)
601				{
602					if(is_file($file) && is_readable($file) && filesize($file) < 1000000)
603					{
604						// copy() can't be used in this case since it will result in a blank file for /proc/ file-system
605						$file_contents = file_get_contents($file);
606						$file_contents = pts_strings::remove_line_timestamps($file_contents);
607						file_put_contents($system_log_dir . basename($file), $file_contents);
608					}
609				}
610
611				$kconfig = null;
612				if(is_file('/proc/config.gz') && pts_client::executable_in_path('zcat'))
613				{
614					$kconfig = shell_exec('zcat /proc/config.gz');
615				}
616				else if(pts_client::executable_in_path('uname') && ($uname_r = trim(shell_exec('uname -r 2>&1'))) && is_file('/boot/config-' . $uname_r))
617				{
618					$kconfig = file_get_contents('/boot/config-' . $uname_r);
619				}
620				if($kconfig != null)
621				{
622					$kconfig = phodevi_vfs::cleanse_and_shorten_kernel_config($kconfig);
623					file_put_contents($system_log_dir . 'config', $kconfig);
624				}
625
626				// Generate logs from system commands to backup
627				$system_log_commands = array(
628					'lspci -mmkvvvnn',
629					'lscpu',
630					'cc -v',
631				//	'lsusb',
632					'lsmod',
633					'sensors',
634					'dmesg',
635					'vdpauinfo',
636					'cpufreq-info',
637					'glxinfo',
638					'clinfo',
639					'vulkaninfo',
640					'uname -a',
641					// 'udisks --dump',
642					//'upower --dump',
643					'dmidecode',
644					);
645
646				if(phodevi::is_linux() && phodevi::read_property('system', 'filesystem') == 'ext4' && phodevi::is_root())
647				{
648					$system_log_commands[] = 'dumpe2fs -h ' . phodevi::read_property('disk', 'device-providing-storage');
649				}
650
651				if(phodevi::is_bsd())
652				{
653					$system_log_commands[] = 'sysctl -a';
654					$system_log_commands[] = 'kenv';
655				}
656
657				foreach($system_log_commands as $command_string)
658				{
659					$command = explode(' ', $command_string);
660
661					if(($command_bin = pts_client::executable_in_path($command[0])))
662					{
663						$cmd_output = shell_exec('cd "' . dirname($command_bin) . '" && ./' . $command_string . ' 2>&1');
664
665						if(strlen($cmd_output) > 900000)
666						{
667							// Don't preserve really large logs, likely filled with lots of junk
668							$cmd_output = null;
669							continue;
670						}
671						if(strpos($cmd_output, 'read kernel buffer failed: Operation not permitted') !== false || strpos($cmd_output, 'Error: unable to open display') !== false)
672						{
673							continue;
674						}
675
676						// Try to filter out any serial numbers, etc.
677						phodevi_vfs::cleanse_file($cmd_output, $command[0]);
678						$cmd_output = pts_strings::remove_line_timestamps($cmd_output);
679
680						file_put_contents($system_log_dir . $command[0], $cmd_output);
681					}
682				}
683
684				// Dump some common / important environmental variables
685				$environment_variables = array(
686					'PATH' => null,
687					'CFLAGS' => null,
688					'CXXFLAGS' => null,
689					'LD_LIBRARY_PATH' => null,
690					'CC' => null,
691					'CXX' => null,
692					'LIBGL_DRIVERS_PATH' => null
693					);
694
695				foreach($environment_variables as $variable => &$value)
696				{
697					$v = getenv($variable);
698
699					if($v != null)
700					{
701						$value = $v;
702					}
703					else
704					{
705						unset($environment_variables[$variable]);
706					}
707				}
708
709				if(!empty($environment_variables))
710				{
711					$variable_dump = null;
712					foreach($environment_variables as $variable => $value)
713					{
714						$variable_dump .= $variable . '=' . $value . PHP_EOL;
715					}
716					file_put_contents($system_log_dir . 'environment-variables', $variable_dump);
717				}
718
719				if(($extra_logs_dir = getenv('PTS_EXTRA_SYSTEM_LOGS_DIR')) != false && is_dir($extra_logs_dir))
720				{
721					// Allow extra arbitrary system logs to be collected within PTS_EXTRA_SYSTEM_LOGS_DIR
722					foreach(pts_file_io::glob($extra_logs_dir . '/*') as $extra_log)
723					{
724						$extra_log_basename = basename($extra_log);
725
726						// Don't overwrite existing auto-generated system log files + also ensure log file is text and not binary etc payload
727						if(!is_file($system_log_dir . $extra_log_basename) && (self::$skip_log_file_type_checks || pts_file_io::is_text_file($extra_log)))
728						{
729							copy($extra_log, $system_log_dir . $extra_log_basename);
730						}
731					}
732				}
733
734				pts_module_manager::module_process('__post_test_run_system_logs', $system_log_dir);
735			}
736		}
737
738		return $bool;
739	}
740	public static function init_display_mode($override_display_mode = false)
741	{
742		if(PTS_IS_WEB_CLIENT && !defined('PHOROMATIC_SERVER'))
743		{
744			self::$display = new pts_web_display_mode();
745			return;
746		}
747
748		$env_mode = pts_client::is_debug_mode() ? 'BASIC' : $override_display_mode;
749
750		switch(($env_mode != false || ($env_mode = pts_client::read_env('PTS_DISPLAY_MODE')) != false ? $env_mode : pts_config::read_user_config('PhoronixTestSuite/Options/General/DefaultDisplayMode', 'DEFAULT')))
751		{
752			case 'BASIC':
753				self::$display = new pts_basic_display_mode();
754				break;
755			case 'BATCH':
756			case 'CONCISE':
757				self::$display = new pts_concise_display_mode();
758				break;
759			case 'SHORT':
760				self::$display = new pts_short_display_mode();
761				break;
762			case 'DEFAULT':
763			default:
764				self::$display = new pts_concise_display_mode();
765				break;
766		}
767	}
768	public static function program_requirement_checks($only_show_required = false, $always_report = false)
769	{
770		$extension_checks = pts_needed_extensions();
771
772		$printed_required_header = false;
773		$printed_optional_header = false;
774		foreach($extension_checks as $extension)
775		{
776			if($extension[1] == false || $always_report)
777			{
778				if($always_report)
779				{
780					$printed_required_header = true;
781					$printed_optional_header = true;
782					echo ($extension[1] == false ? 'MISSING' : 'PRESENT') . ' - ';
783				}
784
785				if($extension[0] == 1)
786				{
787					// Oops, this extension is required
788					if($printed_required_header == false)
789					{
790						echo PHP_EOL . 'The following PHP extensions are REQUIRED:' . PHP_EOL . PHP_EOL;
791						$printed_required_header = true;
792					}
793				}
794				else
795				{
796					if(($only_show_required || PTS_IS_DAEMONIZED_SERVER_PROCESS) && $printed_required_header == false)
797					{
798						continue;
799					}
800
801					// This extension is missing but optional
802					if($printed_optional_header == false)
803					{
804						echo PHP_EOL . ($printed_required_header ? null : 'NOTICE: ') . 'The following PHP extensions are OPTIONAL but recommended:' . PHP_EOL . PHP_EOL;
805						$printed_optional_header = true;
806					}
807				}
808
809				echo sprintf('%-9ls %-30ls' . PHP_EOL, $extension[2], $extension[3]);
810			}
811		}
812
813		if($printed_required_header || $printed_optional_header)
814		{
815			echo PHP_EOL;
816
817			if($printed_required_header && !$always_report)
818			{
819				exit;
820			}
821		}
822	}
823	private static function core_storage_init_process()
824	{
825		$pso = pts_storage_object::recover_from_file(PTS_CORE_STORAGE);
826
827		if($pso == false)
828		{
829			$pso = new pts_storage_object(true, true);
830		}
831
832		// OpenBenchmarking.org - GSID
833		$global_gsid = $pso->read_object('global_system_id');
834		$global_gsid_e = $pso->read_object('global_system_id_e');
835		$global_gsid_p = $pso->read_object('global_system_id_p');
836
837		if(empty($global_gsid) || pts_openbenchmarking::is_valid_gsid_format($global_gsid) == false)
838		{
839			// Global System ID for anonymous uploads, etc
840			$requested_gsid = true;
841			$global_gsid = pts_openbenchmarking_client::request_gsid();
842
843			if(is_array($global_gsid))
844			{
845				$pso->add_object('global_system_id', $global_gsid['gsid']); // GSID
846				$pso->add_object('global_system_id_p', $global_gsid['gsid_p']); // GSID_P
847				$pso->add_object('global_system_id_e', $global_gsid['gsid_e']); // GSID_E
848				pts_define('PTS_GSID', $global_gsid['gsid']);
849				pts_define('PTS_GSID_E', $global_gsid['gsid_e']);
850			}
851		}
852		else if(pts_openbenchmarking::is_valid_gsid_e_format($global_gsid_e) == false || pts_openbenchmarking::is_valid_gsid_p_format($global_gsid_p) == false)
853		{
854			pts_define('PTS_GSID', $global_gsid);
855			$requested_gsid = false;
856			$global_gsid = pts_openbenchmarking_client::retrieve_gsid();
857
858			if(is_array($global_gsid))
859			{
860				$pso->add_object('global_system_id_p', $global_gsid['gsid_p']); // GSID_P
861				$pso->add_object('global_system_id_e', $global_gsid['gsid_e']); // GSID_E
862				pts_define('PTS_GSID_E', $global_gsid['gsid_e']);
863			}
864		}
865		else
866		{
867			pts_define('PTS_GSID', $global_gsid);
868			pts_define('PTS_GSID_E', $global_gsid_e);
869			$requested_gsid = false;
870		}
871
872		$machine_self_id = $pso->read_object('machine_self_id');
873		if(empty($machine_self_id))
874		{
875			$ns = md5('phoronix-test-suite');
876			$binary_ns = null;
877
878			for($i = 0; $i < strlen($ns); $i += 2)
879			{
880				$binary_ns .= chr(hexdec($ns[$i] . $ns[$i + 1]));
881			}
882
883			$msi_hash = sha1($binary_ns . uniqid(PTS_CORE_VERSION, true) . getenv('USERNAME') . getenv('USER') . getenv('HOSTNAME') . phodevi::read_property('network', 'ip'));
884
885			$machine_self_id = sprintf('%08s-%04s-%04x-%04x-%12s', substr($msi_hash, 0, 8), substr($msi_hash, 8, 4), (hexdec(substr($msi_hash, 12, 4)) & 0x0fff) | 0x5000, (hexdec(substr($msi_hash, 16, 4)) & 0x3fff) | 0x8000, substr($msi_hash, 20, 12));
886			// machine_self_id is self-generated unique name for Phoromatic/OB purposes in UUIDv5 format
887			$pso->add_object('machine_self_id', $machine_self_id);
888		}
889		pts_define('PTS_MACHINE_SELF_ID', $machine_self_id);
890
891		// Last Run Processing
892		$last_core_version = $pso->read_object('last_core_version');
893		pts_define('FIRST_RUN_ON_PTS_UPGRADE', ($last_core_version != PTS_CORE_VERSION));
894
895		if(FIRST_RUN_ON_PTS_UPGRADE || ($pso->read_object('last_php_version') != PTS_PHP_VERSION))
896		{
897			// Report any missing/recommended extensions
898			self::program_requirement_checks();
899		}
900
901		if(FIRST_RUN_ON_PTS_UPGRADE)
902		{
903			if($requested_gsid == false && pts_network::internet_support_available())
904			{
905				pts_openbenchmarking_client::update_gsid();
906			}
907
908			$pso->add_object('environmental_variables_for_modules', pts_module_manager::modules_environmental_variables());
909			$pso->add_object('command_alias_list', pts_documentation::client_commands_aliases());
910		}
911		$pso->add_object('last_core_version', PTS_CORE_VERSION); // PTS version last run
912		$pso->add_object('last_php_version', PTS_PHP_VERSION); // PHP version last run
913
914		//$last_pts_version = $pso->read_object('last_pts_version');
915		// do something here with $last_pts_version if you want that information
916		$pso->add_object('last_pts_version', PTS_VERSION); // PTS version last run
917
918		// Last Run Processing
919		$last_run = $pso->read_object('last_run_time');
920		pts_define('TIME_PTS_LAUNCHED', time());
921		pts_define('IS_FIRST_RUN_TODAY', (substr($last_run, 0, 10) != date('Y-m-d', TIME_PTS_LAUNCHED)));
922		$pso->add_object('last_run_time', date('Y-m-d H:i:s', TIME_PTS_LAUNCHED)); // Time PTS was last run
923		self::$time_pts_last_launch = strtotime($last_run);
924		pts_define('TIME_SINCE_LAST_RUN', ceil((TIME_PTS_LAUNCHED - self::$time_pts_last_launch) / 60)); // TIME_SINCE_LAST_RUN is in minutes
925
926		// User Agreement Checking
927		$agreement_cs = $pso->read_object('user_agreement_cs');
928
929		$pso->add_object('user_agreement_cs', $agreement_cs); // User agreement check-sum
930
931		// Phodevi Cache Handling
932		$phodevi_cache = $pso->read_object('phodevi_smart_cache');
933
934		if($phodevi_cache instanceof phodevi_cache && pts_client::read_env('NO_PHODEVI_CACHE') == false)
935		{
936			$phodevi_cache = $phodevi_cache->restore_cache(PTS_USER_PATH, PTS_CORE_VERSION);
937			phodevi::set_device_cache($phodevi_cache);
938
939			if(($external_phodevi_cache = pts_client::read_env('EXTERNAL_PHODEVI_CACHE')))
940			{
941				if(is_dir($external_phodevi_cache) && is_file($external_phodevi_cache . '/core.pt2so'))
942				{
943					$external_phodevi_cache .= '/core.pt2so';
944				}
945
946				if(is_file($external_phodevi_cache))
947				{
948					$external_phodevi_cache = pts_storage_object::force_recover_from_file($external_phodevi_cache);
949
950					if($external_phodevi_cache != false)
951					{
952						$external_phodevi_cache = $external_phodevi_cache->read_object('phodevi_smart_cache');
953						$external_phodevi_cache = $external_phodevi_cache->restore_cache(null, PTS_CORE_VERSION);
954
955						if($external_phodevi_cache != false)
956						{
957							//unset($external_phodevi_cache['system']['operating-system']);
958							//unset($external_phodevi_cache['system']['vendor-identifier']);
959							phodevi::set_device_cache($external_phodevi_cache);
960						}
961					}
962				}
963			}
964		}
965
966		// Archive to disk
967		$pso->save_to_file(PTS_CORE_STORAGE);
968	}
969	public static function get_time_pts_last_started()
970	{
971		return self::$time_pts_last_launch;
972	}
973	public static function register_phoromatic_server($server_ip, $http_port)
974	{
975		self::$phoromatic_servers[] = array('ip' => $server_ip, 'http_port' => $http_port);
976	}
977	public static function available_phoromatic_servers()
978	{
979		if(defined('PHOROMATIC_SERVER') && PHOROMATIC_SERVER)
980		{
981			return array();
982		}
983
984		static $last_phoromatic_scan = 0;
985		static $phoromatic_servers = false;
986
987		// Cache the Phoromatic Server information for 2 minutes since could be expensive to compute
988		if($phoromatic_servers === false || $last_phoromatic_scan < (time() - 120))
989		{
990			$last_phoromatic_scan = time();
991			$phoromatic_servers = array();
992			$possible_servers = pts_network::find_zeroconf_phoromatic_servers(true);
993
994			foreach(self::$phoromatic_servers as $server)
995			{
996				if(phodevi::read_property('network', 'ip') != $server['ip'])
997				{
998					$possible_servers[] = array($server['ip'], $server['http_port']);
999				}
1000			}
1001
1002			$user_config_phoromatic_servers = pts_config::read_user_config('PhoronixTestSuite/Options/General/PhoromaticServers', '');
1003			foreach(explode(',', $user_config_phoromatic_servers) as $static_server)
1004			{
1005				$static_server = explode(':', $static_server);
1006				if(count($static_server) == 2)
1007				{
1008					$possible_servers[] = array($static_server[0], $static_server[1]);
1009				}
1010			}
1011
1012			if(is_file(PTS_USER_PATH . 'phoromatic-servers'))
1013			{
1014				$phoromatic_servers_file = pts_file_io::file_get_contents(PTS_USER_PATH . 'phoromatic-servers');
1015				foreach(explode(PHP_EOL, $phoromatic_servers_file) as $ps_file_line)
1016				{
1017					$ps_file_line = explode(':', trim($ps_file_line));
1018					if(count($ps_file_line) == 2 && ip2long($ps_file_line[0]) !== false && is_numeric($ps_file_line) && $ps_file_line > 100)
1019					{
1020						$possible_servers[] = array($ps_file_line[0], $ps_file_line[1]);
1021					}
1022				}
1023			}
1024
1025			// OpenBenchmarking.org relay zero conf
1026			if(pts_network::internet_support_available())
1027			{
1028				$ob_relay = pts_openbenchmarking::possible_phoromatic_servers();
1029				foreach($ob_relay as $s)
1030				{
1031					$local_ip = phodevi::read_property('network', 'ip');
1032					$local_ip_segments = explode('.', $local_ip);
1033					$s_segments = explode('.', $s[0]);
1034
1035					if($s_segments[0] == $local_ip_segments[0] && $s_segments[1] == $local_ip_segments[1])
1036					{
1037						$possible_servers[] = array($s[0], $s[1]);
1038					}
1039				}
1040			}
1041
1042			foreach($possible_servers as $possible_server)
1043			{
1044				// possible_server[0] is the Phoromatic Server IP
1045				// possible_server[1] is the Phoromatic Server HTTP PORT
1046
1047				if(in_array($possible_server[0], array_keys($phoromatic_servers)) || phodevi::read_property('network', 'ip') ==  $possible_server[0])
1048				{
1049					continue;
1050				}
1051
1052				$server_response = pts_network::http_get_contents('http://' . $possible_server[0] . ':' . $possible_server[1] . '/server.php', false, false, 3);
1053				if(stripos($server_response, 'Phoromatic') !== false)
1054				{
1055					trigger_error('Phoromatic Server Auto-Detected At: ' . $possible_server[0] . ':' . $possible_server[1], E_USER_NOTICE);
1056					$phoromatic_servers[$possible_server[0]] = array('ip' => $possible_server[0], 'http_port' => $possible_server[1]);
1057				}
1058
1059			}
1060		}
1061
1062		return $phoromatic_servers;
1063	}
1064	public static function user_agreement_check($command)
1065	{
1066		$pso = pts_storage_object::recover_from_file(PTS_CORE_STORAGE);
1067
1068		if($pso == false)
1069		{
1070			return false;
1071		}
1072
1073		$config_md5 = $pso->read_object('user_agreement_cs');
1074		$current_md5 = md5_file(PTS_PATH . 'pts-core/user-agreement.txt');
1075
1076		if(($config_md5 != $current_md5 || pts_config::read_user_config('PhoronixTestSuite/Options/OpenBenchmarking/AnonymousUsageReporting', 'UNKNOWN') == 'UNKNOWN') && !PTS_IS_DAEMONIZED_SERVER_PROCESS && getenv('PTS_SILENT_MODE') != 1 && $config_md5 != 'enterprise-agree')
1077		{
1078			$prompt_in_method = pts_client::check_command_for_function($command, 'pts_user_agreement_prompt');
1079			$user_agreement = file_get_contents(PTS_PATH . 'pts-core/user-agreement.txt');
1080
1081			if($prompt_in_method)
1082			{
1083				$user_agreement_return = call_user_func(array($command, 'pts_user_agreement_prompt'), $user_agreement);
1084
1085				if(is_array($user_agreement_return))
1086				{
1087					if(count($user_agreement_return) == 3)
1088					{
1089						list($agree, $usage_reporting) = $user_agreement_return;
1090					}
1091					else
1092					{
1093						$agree = array_shift($user_agreement_return);
1094						$usage_reporting = -1;
1095					}
1096				}
1097				else
1098				{
1099					$agree = $user_agreement_return;
1100					$usage_reporting = -1;
1101				}
1102			}
1103
1104			if($prompt_in_method == false || $usage_reporting == -1)
1105			{
1106				pts_client::$display->generic_heading('User Agreement');
1107				echo wordwrap($user_agreement, (pts_client::terminal_width() - 2));
1108				$agree = pts_user_io::prompt_bool_input('Do you agree to these terms and wish to proceed', -1);
1109
1110				if(!pts_openbenchmarking::ob_upload_support_available())
1111				{
1112					$usage_reporting = false;
1113				}
1114				else
1115				{
1116					$usage_reporting = $agree ? pts_user_io::prompt_bool_input('Enable anonymous usage / statistics reporting', -1) : -1;
1117				}
1118			}
1119
1120			if($agree)
1121			{
1122				echo PHP_EOL;
1123				$pso->add_object('user_agreement_cs', $current_md5);
1124				$pso->save_to_file(PTS_CORE_STORAGE);
1125			}
1126			else
1127			{
1128				pts_client::exit_client('In order to run the Phoronix Test Suite, you must agree to the listed terms.');
1129			}
1130
1131			pts_config::user_config_generate(array(
1132				'PhoronixTestSuite/Options/OpenBenchmarking/AnonymousUsageReporting' => pts_config::bool_to_string($usage_reporting)));
1133		}
1134
1135		if(PTS_IS_CLIENT && getenv('PTS_SILENT_MODE') != 1)
1136		{
1137			pts_external_dependencies::startup_handler();
1138		}
1139	}
1140	public static function swap_variables($user_str, $replace_call)
1141	{
1142		if(is_array($replace_call))
1143		{
1144			if(count($replace_call) != 2 || method_exists($replace_call[0], $replace_call[1]) == false)
1145			{
1146				echo PHP_EOL . 'Var Swap With Method Failed.' . PHP_EOL;
1147				return $user_str;
1148			}
1149		}
1150		else if(!function_exists($replace_call))
1151		{
1152			echo PHP_EOL . 'Var Swap With Function Failed.' . PHP_EOL;
1153			return $user_str;
1154		}
1155
1156		$offset = 0;
1157		$replace_call_return = false;
1158
1159		while($offset < strlen($user_str) && ($s = strpos($user_str, '$', $offset)) !== false)
1160		{
1161			$s++;
1162			$var_name = substr($user_str, $s, (($e = strpos($user_str, ' ', $s)) == false ? strlen($user_str) : $e) - $s);
1163
1164			if($replace_call_return === false)
1165			{
1166				$replace_call_return = call_user_func($replace_call);
1167			}
1168
1169			$var_replacement = isset($replace_call_return[$var_name]) ? $replace_call_return[$var_name] : null;
1170
1171			if($var_replacement != null)
1172			{
1173				$user_str = str_replace('$' . $var_name, $var_replacement, $user_str);
1174			}
1175			else
1176			{
1177				// echo "\nVariable Swap For $var_name Failed.\n";
1178			}
1179
1180			$offset = $s + strlen($var_replacement);
1181		}
1182
1183		return $user_str;
1184	}
1185	public static function setup_test_result_directory($save_to)
1186	{
1187		$save_to_dir = PTS_SAVE_RESULTS_PATH . $save_to;
1188
1189		if(strpos(basename($save_to_dir), '.'))
1190		{
1191			$save_to_dir = dirname($save_to_dir);
1192		}
1193
1194		if($save_to_dir != '.')
1195		{
1196			pts_file_io::mkdir($save_to_dir);
1197		}
1198
1199		return $save_to_dir;
1200	}
1201	public static function remove_installed_test(&$test_profile)
1202	{
1203		pts_file_io::delete($test_profile->get_install_dir(), null, true);
1204	}
1205	public static function exit_client($string = null, $exit_status = 0)
1206	{
1207		// Exit the Phoronix Test Suite client
1208		pts_define('PTS_EXIT', 1);
1209
1210		if($string != null)
1211		{
1212			echo PHP_EOL . $string . PHP_EOL;
1213		}
1214
1215		exit($exit_status);
1216	}
1217	public static function current_user()
1218	{
1219		// Current system user
1220		return ($pts_user = pts_openbenchmarking_client::user_name()) != null ? $pts_user : phodevi::read_property('system', 'username');
1221	}
1222	public static function generate_result_file_graphs($test_results_identifier, $save_to_dir = false, $extra_attributes = null)
1223	{
1224		// Since dropping the old result viewer, this function is no longer used except for niche cases (debug render, PDF generation)
1225
1226		if($save_to_dir)
1227		{
1228			if(pts_file_io::mkdir($save_to_dir . '/result-graphs') == false)
1229			{
1230				// Don't delete old files now, in case any modules (e.g. FlameGrapher) output something in there ahead of time
1231				/*// Directory must exist, so remove any old graph files first
1232				foreach(pts_file_io::glob($save_to_dir . '/result-graphs/*') as $old_file)
1233				{
1234					unlink($old_file);
1235				}*/
1236			}
1237		}
1238
1239		if($test_results_identifier instanceof pts_result_file)
1240		{
1241			$result_file = &$test_results_identifier;
1242		}
1243		else
1244		{
1245			$result_file = new pts_result_file($test_results_identifier);
1246		}
1247
1248		$result_file->avoid_duplicate_identifiers();
1249
1250		$generated_graphs = array();
1251		$generated_graph_tables = false;
1252
1253		// Render overview chart
1254		if($save_to_dir)
1255		{
1256			$chart = new pts_ResultFileTable($result_file);
1257			$chart->renderChart($save_to_dir . '/result-graphs/overview.BILDE_EXTENSION');
1258
1259			$intent = -1;
1260			if(($intent = pts_result_file_analyzer::analyze_result_file_intent($result_file, $intent, true)) || $result_file->get_system_count() == 1)
1261			{
1262				$chart = new pts_ResultFileCompactSystemsTable($result_file, $intent);
1263			}
1264			else
1265			{
1266				$chart = new pts_ResultFileSystemsTable($result_file);
1267			}
1268			$chart->renderChart($save_to_dir . '/result-graphs/systems.BILDE_EXTENSION');
1269			unset($chart);
1270
1271			if($intent && is_dir($result_file->get_system_log_dir()))
1272			{
1273				$chart = new pts_DetailedSystemComponentTable($result_file, $result_file->get_system_log_dir(), $intent);
1274
1275				if($chart)
1276				{
1277					$chart->renderChart($save_to_dir . '/result-graphs/detailed_component.BILDE_EXTENSION');
1278				}
1279			}
1280		}
1281		$result_objects = $result_file->get_result_objects();
1282		$test_titles = array();
1283		foreach($result_objects as &$result_object)
1284		{
1285			$test_titles[] = $result_object->test_profile->get_title();
1286		}
1287
1288		$offset = 0;
1289		foreach($result_objects as $key => &$result_object)
1290		{
1291			$save_to = $save_to_dir;
1292			$offset++;
1293
1294			if($save_to_dir && is_dir($save_to_dir))
1295			{
1296				$save_to .= '/result-graphs/' . $offset . '.BILDE_EXTENSION';
1297
1298				if(PTS_IS_CLIENT)
1299				{
1300					if($result_file->is_multi_way_comparison(null, $extra_attributes) || pts_client::read_env('GRAPH_GROUP_SIMILAR'))
1301					{
1302						$table_keys = array();
1303
1304						foreach($test_titles as $this_title_index => $this_title)
1305						{
1306							if(isset($test_titles[$key]) && $this_title == $test_titles[$key])
1307							{
1308								$table_keys[] = $this_title_index;
1309							}
1310						}
1311					}
1312					else
1313					{
1314						$table_keys = $key;
1315					}
1316
1317					$chart = new pts_ResultFileTable($result_file, null, $table_keys);
1318					$chart->renderChart($save_to_dir . '/result-graphs/' . $offset . '_table.BILDE_EXTENSION');
1319					unset($chart);
1320					$generated_graph_tables = true;
1321				}
1322			}
1323
1324			$graph = pts_render::render_graph($result_object, $result_file, $save_to, $extra_attributes);
1325
1326			if($graph == false)
1327			{
1328				continue;
1329			}
1330
1331			$generated_graphs[] = $graph;
1332		}
1333
1334		// Generate mini / overview graphs
1335		if($save_to_dir)
1336		{
1337			$graph = new pts_OverviewGraph($result_file);
1338			$rendered = $graph->renderGraph();
1339
1340			// Check to see if skip_graph was realized during the rendering process
1341			if($rendered)
1342			{
1343				$graph->svg_dom->output($save_to_dir . '/result-graphs/visualize.BILDE_EXTENSION');
1344			}
1345			unset($graph);
1346
1347			if($result_file->get_system_count() == 2)
1348			{
1349				$graph = new pts_graph_run_vs_run($result_file);
1350			}
1351			else
1352			{
1353				$graph = new pts_graph_radar_chart($result_file);
1354			}
1355
1356			$rendered = $graph->renderGraph();
1357
1358			// Check to see if skip_graph was realized during the rendering process
1359			if($rendered)
1360			{
1361				$graph->svg_dom->output($save_to_dir . '/result-graphs/radar.BILDE_EXTENSION');
1362			}
1363			unset($graph);
1364		}
1365
1366		return $generated_graphs;
1367	}
1368
1369	public static function process_shutdown_tasks()
1370	{
1371		// TODO: possibly do something like posix_getpid() != pts_client::$startup_pid in case shutdown function is called from a child process
1372		// Generate Phodevi Smart Cache
1373		if(pts_client::read_env('NO_PHODEVI_CACHE') == false && pts_client::read_env('EXTERNAL_PHODEVI_CACHE') == false)
1374		{
1375			if(pts_config::read_bool_config('PhoronixTestSuite/Options/General/UsePhodeviCache', 'TRUE'))
1376			{
1377				pts_storage_object::set_in_file(PTS_CORE_STORAGE, 'phodevi_smart_cache', phodevi::get_phodevi_cache_object(PTS_USER_PATH, PTS_CORE_VERSION));
1378			}
1379			else
1380			{
1381				pts_storage_object::set_in_file(PTS_CORE_STORAGE, 'phodevi_smart_cache', null);
1382			}
1383		}
1384
1385		if(is_array(self::$lock_pointers))
1386		{
1387			foreach(array_keys(self::$lock_pointers) as $lock_file)
1388			{
1389				self::release_lock($lock_file);
1390			}
1391		}
1392
1393		foreach(self::$forked_pids as $pid)
1394		{
1395			if(is_dir('/proc/' . $pid) && function_exists('posix_kill'))
1396			{
1397				posix_kill($pid, SIGKILL);
1398			}
1399		}
1400	}
1401	public static function kill_process_with_children_processes($pid)
1402	{
1403		if(is_dir('/proc/' . $pid) && is_file('/proc/' . $pid . '/task/' . $pid . '/children'))
1404		{
1405			$child_processes = pts_strings::trim_explode(' ', file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
1406
1407			foreach($child_processes as $p)
1408			{
1409				if(!empty($p) && is_dir('/proc/' . $p))
1410				{
1411					self::kill_process_with_children_processes($p);
1412				}
1413			}
1414		}
1415		if(!empty($pid) && is_dir('/proc/' . $pid))
1416		{
1417			if(function_exists('posix_kill'))
1418			{
1419				posix_kill($pid, SIGKILL);
1420			}
1421			else
1422			{
1423				shell_exec('kill -9 ' . $pid);
1424			}
1425			sleep(1);
1426		}
1427	}
1428	public static function do_anonymous_usage_reporting()
1429	{
1430		return pts_config::read_bool_config('PhoronixTestSuite/Options/OpenBenchmarking/AnonymousUsageReporting', 0);
1431	}
1432	public static function check_command_for_function($option, $check_function)
1433	{
1434		$in_option = false;
1435
1436		if(is_file(PTS_COMMAND_PATH . $option . '.php'))
1437		{
1438			if(!class_exists($option, false) && is_file(PTS_COMMAND_PATH . $option . '.php'))
1439			{
1440				include(PTS_COMMAND_PATH . $option . '.php');
1441			}
1442
1443			if(method_exists($option, $check_function))
1444			{
1445				$in_option = true;
1446			}
1447		}
1448
1449		return $in_option;
1450	}
1451	public static function execute_command($command, $pass_args = null)
1452	{
1453		if(!class_exists($command, false) && is_file(PTS_COMMAND_PATH . $command . '.php'))
1454		{
1455			include(PTS_COMMAND_PATH . $command . '.php');
1456		}
1457
1458		if(is_file(PTS_COMMAND_PATH . $command . '.php') && method_exists($command, 'argument_checks'))
1459		{
1460			$argument_checks = call_user_func(array($command, 'argument_checks'));
1461
1462			foreach($argument_checks as &$argument_check)
1463			{
1464				$function_check = $argument_check->get_function_check();
1465				$method_check = false;
1466
1467				if(is_array($function_check) && count($function_check) == 2)
1468				{
1469					$method_check = $function_check[0];
1470					$function_check = $function_check[1];
1471				}
1472
1473				if(substr($function_check, 0, 1) == '!')
1474				{
1475					$function_check = substr($function_check, 1);
1476					$return_fails_on = true;
1477				}
1478				else
1479				{
1480					$return_fails_on = false;
1481				}
1482
1483
1484				if($method_check != false)
1485				{
1486					if(!method_exists($method_check, $function_check))
1487					{
1488						echo PHP_EOL . 'Method check fails.' . PHP_EOL;
1489						continue;
1490					}
1491
1492					$function_check = array($method_check, $function_check);
1493				}
1494				else if(!function_exists($function_check))
1495				{
1496					continue;
1497				}
1498
1499				// VARIABLE_LENGTH_MAYBE when handling is optional or VARIABLE_LENGTH
1500				if(($maybe = ($argument_check->get_argument_index() === 'VARIABLE_LENGTH_MAYBE')) || $argument_check->get_argument_index() == 'VARIABLE_LENGTH')
1501				{
1502					$return_value = $maybe ? true : null;
1503
1504					foreach($pass_args as $arg)
1505					{
1506						$return_value = call_user_func_array($function_check, array($arg));
1507
1508						if($return_value == true)
1509						{
1510							break;
1511						}
1512					}
1513
1514				}
1515				else
1516				{
1517					$return_value = call_user_func_array($function_check, array((isset($pass_args[$argument_check->get_argument_index()]) ? $pass_args[$argument_check->get_argument_index()] : null)));
1518				}
1519
1520				if($return_value == $return_fails_on)
1521				{
1522					$command_alias = defined($command . '::doc_use_alias') ? constant($command . '::doc_use_alias') : $command;
1523
1524					if((isset($pass_args[$argument_check->get_argument_index()]) && !empty($pass_args[$argument_check->get_argument_index()])) || ($argument_check->get_argument_index() == 'VARIABLE_LENGTH' && !empty($pass_args)))
1525					{
1526						trigger_error('Invalid Argument: ' . implode(' ', $pass_args), E_USER_ERROR);
1527					}
1528					else
1529					{
1530						trigger_error('Phoronix Test Suite Argument Missing', E_USER_ERROR);
1531					}
1532
1533					echo PHP_EOL . pts_client::cli_just_bold('CORRECT SYNTAX:') . PHP_EOL . 'phoronix-test-suite ' . str_replace('_', '-', $command_alias) . ' ' . pts_client::cli_just_italic(implode(' ', $argument_checks)) . PHP_EOL . PHP_EOL;
1534					pts_client::invalid_command_helper($pass_args, $argument_checks);
1535
1536					return false;
1537				}
1538				else
1539				{
1540					if($argument_check->get_function_return_key() != null && !isset($pass_args[$argument_check->get_function_return_key()]))
1541					{
1542						$pass_args[$argument_check->get_function_return_key()] = $return_value;
1543					}
1544				}
1545			}
1546		}
1547
1548		pts_module_manager::module_process('__pre_option_process', $command);
1549
1550		if(is_file(PTS_COMMAND_PATH . $command . '.php'))
1551		{
1552			self::$current_command = $command;
1553
1554			if(method_exists($command, 'run'))
1555			{
1556				call_user_func(array($command, 'run'), $pass_args);
1557			}
1558			else
1559			{
1560				echo PHP_EOL . 'There is an error in the requested command: ' . $command . PHP_EOL . PHP_EOL;
1561			}
1562		}
1563		else if(($t = pts_module::valid_run_command($command)) != false)
1564		{
1565			list($module, $module_command) = $t;
1566			pts_module_manager::set_current_module($module);
1567			pts_module_manager::run_command($module, $module_command, $pass_args);
1568			pts_module_manager::set_current_module(null);
1569		}
1570		echo PHP_EOL;
1571
1572		pts_module_manager::module_process('__post_option_process', $command);
1573	}
1574	public static function invalid_command_helper($passed_args, &$argument_checks)
1575	{
1576		$supports_passing_a_test = false;
1577		foreach($argument_checks as $check)
1578		{
1579			if($check->get_function_check_type() == 'Test' || strpos($check->get_function_check_type(), 'Test |') !== false)
1580			{
1581				$supports_passing_a_test = true;
1582			}
1583		}
1584
1585		$showed_recent_results = pts_tests::recently_saved_results();
1586
1587		if($supports_passing_a_test)
1588		{
1589			$tests_to_show = array_keys(pts_openbenchmarking_client::new_and_recently_updated_tests(30, 31, true));
1590			$tests_to_show_title = 'New Tests';
1591
1592			if(count($tests_to_show) < 3)
1593			{
1594				$tests_to_show = array_keys(pts_openbenchmarking_client::new_and_recently_updated_tests(60, 31));
1595				$tests_to_show_title = 'New + Updated Tests';
1596			}
1597
1598			if(count($tests_to_show) < 3)
1599			{
1600				$tests_to_show = array_keys(pts_openbenchmarking_client::most_popular_tests(20));
1601				$tests_to_show_title = 'Popular Tests';
1602			}
1603
1604			if(count($tests_to_show) > 3)
1605			{
1606				$longest_test = strlen(pts_strings::find_longest_string($tests_to_show)) + 3;
1607				$terminal_width = pts_client::terminal_width();
1608				$tests_per_line = floor($terminal_width / $longest_test);
1609				shuffle($tests_to_show);
1610				$tests_to_show = array_slice($tests_to_show, 0, min(count($tests_to_show), $tests_per_line * 3 - 1));
1611
1612				echo pts_client::cli_just_bold($tests_to_show_title . ':') . PHP_EOL;
1613				$i = 0;
1614				foreach($tests_to_show as $test)
1615				{
1616					if($i % $tests_per_line == 0)
1617					{
1618						echo '   ';
1619					}
1620					echo $test;
1621
1622					$i++;
1623					if($i % $tests_per_line == 0 || $i == count($tests_to_show))
1624					{
1625						echo PHP_EOL;
1626					}
1627					else
1628					{
1629						echo str_repeat(' ', $longest_test - strlen($test));
1630					}
1631				}
1632			}
1633		}
1634
1635		// Disable this for now to cutdown on server resources and since not too useful
1636		// same info can be gathered from `phoronix-test-suite openbenchmarking-uploads`
1637		/*
1638		if(count($result_uploads = pts_openbenchmarking::result_uploads_from_this_ip()) > 0)
1639		{
1640			echo PHP_EOL . pts_client::cli_just_bold('Recent OpenBenchmarking.org Results From This IP:') . PHP_EOL;
1641			$t = array();
1642			foreach($result_uploads as $id => $title)
1643			{
1644				$t[] = array(pts_client::cli_colored_text($id, 'gray', true), $title);
1645
1646				if(count($t) == 5)
1647				{
1648					break;
1649				}
1650			}
1651			echo pts_user_io::display_text_table($t, '   ') . PHP_EOL;
1652		}
1653		*/
1654		echo PHP_EOL;
1655
1656		$similar_tests = array();
1657		if(!empty($passed_args))
1658		{
1659			foreach(pts_arrays::to_array($passed_args) as $passed_arg)
1660			{
1661				$arg_soundex = soundex($passed_arg);
1662				$arg_save_identifier_like = pts_test_run_manager::clean_save_name($passed_arg);
1663
1664				foreach(pts_openbenchmarking::linked_repositories() as $repo)
1665				{
1666					$repo_index = pts_openbenchmarking::read_repository_index($repo);
1667
1668					foreach(array('tests', 'suites') as $type)
1669					{
1670						if(isset($repo_index[$type]) && is_array($repo_index[$type]))
1671						{
1672							foreach(array_keys($repo_index[$type]) as $identifier)
1673							{
1674								if(soundex($identifier) == $arg_soundex)
1675								{
1676									pts_arrays::unique_push($similar_tests, array($identifier, ' [' . ucwords(substr($type, 0, -1)) . ']'));
1677								}
1678								else if(isset($passed_arg[3]) && strpos($identifier, $passed_arg) !== false)
1679								{
1680									pts_arrays::unique_push($similar_tests, array($identifier, ' [' . ucwords(substr($type, 0, -1)) . ']'));
1681								}
1682							}
1683						}
1684					}
1685				}
1686
1687				foreach(pts_results::saved_test_results() as $result)
1688				{
1689					if(soundex($result) == $arg_soundex || (isset($passed_arg[3]) && strpos($identifier, $arg_save_identifier_like) !== false))
1690					{
1691						pts_arrays::unique_push($similar_tests, array($result, ' [Test Result]'));
1692					}
1693				}
1694
1695				if(strpos($passed_arg, '-') !== false)
1696				{
1697					$possible_identifier = str_replace('-', '', $passed_arg);
1698					if(pts_test_profile::is_test_profile($possible_identifier))
1699					{
1700						pts_arrays::unique_push($similar_tests, array($possible_identifier, ' [Test]'));
1701					}
1702				}
1703				if($passed_arg != ($possible_identifier = strtolower($passed_arg)))
1704				{
1705					if(pts_test_profile::is_test_profile($possible_identifier))
1706					{
1707						pts_arrays::unique_push($similar_tests, array($possible_identifier, ' [Test]'));
1708					}
1709				}
1710			}
1711		}
1712		if(count($similar_tests) > 0)
1713		{
1714			echo pts_client::cli_just_bold('Possible Suggestions:') . PHP_EOL;
1715			//$similar_tests = array_unique($similar_tests);
1716			if(isset($similar_tests[12]))
1717			{
1718				// lots of tests... trim it down
1719				$similar_tests = array_rand($similar_tests, 12);
1720			}
1721			echo pts_user_io::display_text_table($similar_tests, '- ') . PHP_EOL . PHP_EOL;
1722		}
1723
1724		if($showed_recent_results == false)
1725		{
1726			echo 'See available tests to run by visiting OpenBenchmarking.org or running:' . PHP_EOL . PHP_EOL;
1727			echo '    phoronix-test-suite list-tests' . PHP_EOL . PHP_EOL;
1728			echo 'Tests can be installed by running:' . PHP_EOL . PHP_EOL;
1729			echo '    phoronix-test-suite install <test-name>' . PHP_EOL . PHP_EOL;
1730		}
1731	}
1732	public static function get_sent_command()
1733	{
1734		return self::$sent_command;
1735	}
1736	public static function handle_sent_command(&$sent_command, &$argv, &$argc)
1737	{
1738		self::$sent_command = $sent_command;
1739		if(is_file(PTS_PATH . 'pts-core/commands/' . $sent_command . '.php') == false)
1740		{
1741			$replaced = false;
1742
1743			if(pts_module::valid_run_command($sent_command))
1744			{
1745				$replaced = true;
1746			}
1747			else if(isset($argv[1]) && strpos($argv[1], '.openbenchmarking') !== false && is_readable($argv[1]))
1748			{
1749				$sent_command = 'openbenchmarking_launcher';
1750				$argv[2] = $argv[1];
1751				$argc = 3;
1752				$replaced = true;
1753			}
1754			else
1755			{
1756				$aliases = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'command_alias_list');
1757				if($aliases == null)
1758				{
1759					$aliases = pts_documentation::client_commands_aliases();
1760				}
1761
1762				if(isset($aliases[$sent_command]))
1763				{
1764					$sent_command = $aliases[$sent_command];
1765					$replaced = true;
1766				}
1767			}
1768
1769			if($replaced == false)
1770			{
1771				// Show help command, since there are no valid commands
1772				$sent_command = 'help';
1773			}
1774		}
1775		else
1776		{
1777			$replaced = true;
1778		}
1779
1780		return $replaced;
1781	}
1782	public static function current_command()
1783	{
1784		return self::$current_command;
1785	}
1786	public static function terminal_width()
1787	{
1788		static $terminal_width = null;
1789
1790		// XXX As of PTS 8.6.1, no longer caching this as not sure it makes sense to do... So be more responsive about terminal resizing
1791		// Or at least cache on Windows where powershell calls can take longer
1792		if(!phodevi::is_windows() || $terminal_width == null)
1793		{
1794			$terminal_width = 80;
1795
1796			if(phodevi::is_windows())
1797			{
1798				// Powershell defaults to 120
1799				$terminal_width = trim(shell_exec('powershell "(get-host).UI.RawUI.MaxWindowSize.width"'));
1800			}
1801			else if(pts_client::read_env('TERMINAL_WIDTH') != false && is_numeric(pts_client::read_env('TERMINAL_WIDTH')) >= 20)
1802			{
1803				$terminal_width = pts_client::read_env('TERMINAL_WIDTH');
1804			}
1805			else if(pts_client::executable_in_path('stty'))
1806			{
1807				$terminal_width = explode(' ', trim(shell_exec('stty size 2>&1')));
1808
1809				if(count($terminal_width) == 2 && is_numeric($terminal_width[1]) && $terminal_width[1] >= 40)
1810				{
1811					$terminal_width = $terminal_width[1];
1812				}
1813				else
1814				{
1815					$terminal_width = 80;
1816				}
1817			}
1818			else if(pts_client::executable_in_path('tput'))
1819			{
1820				$terminal_width = trim(shell_exec('tput cols 2>&1'));
1821
1822				if(is_numeric($terminal_width) && $terminal_width > 1)
1823				{
1824					$terminal_width = $terminal_width;
1825				}
1826				else
1827				{
1828					$terminal_width = 80;
1829				}
1830			}
1831		}
1832
1833		return $terminal_width;
1834	}
1835	public static function terminal_height()
1836	{
1837		static $terminal_height = null;
1838
1839		if($terminal_height == null)
1840		{
1841			$terminal_height = 12;
1842
1843			if(phodevi::is_windows())
1844			{
1845				$terminal_height = trim(shell_exec('powershell "(get-host).UI.RawUI.MaxWindowSize.height"'));
1846			}
1847			else if(pts_client::executable_in_path('stty'))
1848			{
1849				$th = explode(' ', trim(shell_exec('stty size 2>&1')));
1850
1851				if(count($th) == 2 && is_numeric($th[0]) && $th[0] >= 1)
1852				{
1853					$terminal_height = $th[1];
1854				}
1855			}
1856			else if(pts_client::executable_in_path('tput'))
1857			{
1858				$th = trim(shell_exec('tput lines 2>&1'));
1859
1860				if(is_numeric($th) && $th > 1)
1861				{
1862					$terminal_height = $th;
1863				}
1864			}
1865		}
1866
1867		return $terminal_height;
1868	}
1869	public static function is_process_running($process)
1870	{
1871		$running = null;
1872		if(phodevi::is_linux() && pts_client::executable_in_path('ps'))
1873		{
1874			// Checks if process is running on the system
1875			$ps = shell_exec('ps -C ' . strtolower($process) . ' 2>&1');
1876			if(strpos($ps, 'unrecognized option') === false)
1877			{
1878				$running = trim(str_replace(array('PID', 'TTY', 'TIME', 'CMD'), '', $ps));
1879			}
1880		}
1881		else if(phodevi::is_solaris())
1882		{
1883			// Checks if process is running on the system
1884			$ps = shell_exec('ps -ef 2>&1');
1885			$running = strpos($ps, ' ' . strtolower($process)) != false ? 'TRUE' : null;
1886		}
1887		else if(pts_client::executable_in_path('ps') != false)
1888		{
1889			// Checks if process is running on the system
1890			$ps = shell_exec('ps -ax 2>&1');
1891			if(strpos($ps, 'unrecognized option') === false)
1892			{
1893				$running = strpos($ps, strtolower($process)) != false ? 'TRUE' : null;
1894			}
1895		}
1896
1897		return !empty($running);
1898	}
1899	public static function parse_value_string_double_identifier($value_string)
1900	{
1901		// i.e. with PRESET_OPTIONS='stream.run-type=Add'
1902		$values = array();
1903
1904		foreach(explode(';', $value_string) as $preset)
1905		{
1906			if(count($preset = pts_strings::trim_explode('=', $preset)) == 2)
1907			{
1908				$dot = strrpos($preset[0], '.');
1909				if($dot !== false && ($test = substr($preset[0], 0, $dot)) != null && ($option = substr($preset[0], ($dot + 1))) != null)
1910				{
1911					$values[$test][$option] = $preset[1];
1912				}
1913			}
1914		}
1915
1916		return $values;
1917	}
1918	public static function create_temporary_file($file_extension = null)
1919	{
1920		$temp_file = tempnam(pts_client::temporary_directory(), 'PTS');
1921
1922		if($file_extension)
1923		{
1924			$extended_file = pts_client::temporary_directory() . '/' . basename($temp_file) . $file_extension;
1925
1926			if(rename($temp_file, $extended_file))
1927			{
1928				$temp_file = $extended_file;
1929			}
1930		}
1931
1932		return $temp_file;
1933	}
1934	public static function create_temporary_directory($prefix = null, $large_file_support = false)
1935	{
1936		$tmpdir = pts_client::temporary_directory();
1937		if($large_file_support && disk_free_space(PTS_USER_PATH) > disk_free_space($tmpdir))
1938		{
1939			$tmpdir = PTS_USER_PATH . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
1940			pts_file_io::mkdir($tmpdir);
1941		}
1942
1943		do
1944		{
1945			$randname = '/pts-' . $prefix . rand(0, 9999);
1946		}
1947		while(is_dir($tmpdir . $randname));
1948
1949		mkdir($tmpdir . $randname);
1950
1951		return $tmpdir . $randname . '/';
1952	}
1953	public static function temporary_directory()
1954	{
1955		return sys_get_temp_dir();
1956	}
1957	public static function read_env($var)
1958	{
1959		return getenv($var);
1960	}
1961	public static function pts_set_environment_variable($name, $value)
1962	{
1963		// Sets an environmental variable
1964		return getenv($name) == false && putenv($name . '=' . $value);
1965	}
1966	public static function shell_exec($exec, $extra_vars = null)
1967	{
1968		// Same as shell_exec() but with the PTS env variables added in
1969		// Convert pts_client::environmental_variables() into shell export variable syntax
1970
1971		$var_string = '';
1972		$extra_vars = ($extra_vars == null ? pts_client::environmental_variables() : array_merge(pts_client::environmental_variables(), $extra_vars));
1973
1974		foreach(array_keys($extra_vars) as $key)
1975		{
1976			if(phodevi::is_windows())
1977			{
1978				$v = str_replace('"', '', trim($extra_vars[$key]));
1979				if(substr($v, -1) == '\\')
1980				{ echo $v;
1981					$v = substr($v, 0, -1);
1982				}
1983				$var_string .= 'setx ' . $key . ' "' . $v . '";';
1984			}
1985			else
1986			{
1987				$var_string .= 'export ' . $key . '="' . str_replace(' ', '\ ', trim($extra_vars[$key])) . '";';
1988			}
1989		}
1990		$var_string .= ' ';
1991
1992		return shell_exec($var_string . $exec);
1993	}
1994	public static function get_path()
1995	{
1996		$path = pts_client::read_env('PATH');
1997		if(empty($path) || $path == ':')
1998		{
1999			if(phodevi::is_windows())
2000			{
2001				$path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Users\\' . getenv('USERNAME') . '\AppData\Local\Microsoft\WindowsApps;';
2002			}
2003			else
2004			{
2005				$path = '/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/pkg/bin:/usr/games';
2006			}
2007		}
2008		if(phodevi::is_windows())
2009		{
2010			$possible_paths_to_add = array('C:\Users\\' . getenv('USERNAME') . '\AppData\Local\Programs\Python\Python36-32',
2011				'C:\Users\\' . getenv('USERNAME') . '\AppData\Local\Programs\Python\Python37',
2012				'C:\Users\\' . getenv('USERNAME') . '\AppData\Local\Programs\Python\Python38',
2013				'C:\Users\\' . getenv('USERNAME') . '\AppData\Local\Programs\Python\Python39',
2014				'C:\Python27',
2015				'C:\Go\bin',
2016				'C:\Strawberry\perl\bin',
2017				pts_file_io::glob('C:\*\NVIDIA*\NVSMI'), // NVIDIA SMI
2018				pts_file_io::glob('C:\*\R\R-*\bin'),
2019				pts_file_io::glob('C:\*\Java\jdk-*\bin'), pts_file_io::glob('C:\*\ojdkbuild\java-*\bin'), pts_file_io::glob('C:\*\Java\jre-*\bin'),
2020				'C:\cygwin64\bin',
2021				pts_file_io::glob('C:\Program*\LLVM\bin'),
2022				pts_file_io::glob('C:\Program*\CMake\bin'),
2023				pts_file_io::glob('C:\Program*\WinRAR')
2024				);
2025			foreach($possible_paths_to_add as $path_check)
2026			{
2027				if(is_array($path_check))
2028				{
2029					// if it's an array it came from glob so no need to re-check if is_dir()
2030					foreach($path_check as $sub_check)
2031					{
2032						if(strpos($path, $sub_check) == false)
2033						{
2034							$path .= ';' . $sub_check;
2035						}
2036					}
2037				}
2038				else if(is_dir($path_check) && strpos($path, $path_check) == false)
2039				{
2040					$path .= ';' . $path_check;
2041				}
2042			}
2043		}
2044		else
2045		{
2046			// Fedora OpenMPI path not often in PATH by default
2047			$ds = array('/usr/lib64/openmpi/bin',
2048				'/usr/local/mpi/openmpi/bin'
2049				);
2050			foreach($ds as $d)
2051			{
2052				if(is_dir($d) && strpos($path, $d) === false)
2053				{
2054					$path .= ':' . $d;
2055				}
2056			}
2057		}
2058
2059		return $path;
2060	}
2061	public static function get_path_separator()
2062	{
2063		return phodevi::is_windows() ? ';' : ':';
2064	}
2065	public static function executable_in_path($executable, $ignore_paths_with = false)
2066	{
2067		static $cache = null;
2068
2069		if(!isset($cache[$executable]) || empty($cache[$executable]) || $ignore_paths_with)
2070		{
2071			$path = pts_client::get_path();
2072			$paths = pts_strings::trim_explode(pts_client::get_path_separator(), $path);
2073			$executable_path = false;
2074
2075			foreach($paths as $path)
2076			{
2077				$path = pts_strings::add_trailing_slash($path);
2078
2079				if(is_executable($path . $executable))
2080				{
2081					if($ignore_paths_with && stripos($path, $ignore_paths_with) !== false)
2082					{
2083						continue;
2084					}
2085
2086					$executable_path = $path . $executable;
2087					break;
2088				}
2089			}
2090
2091			if($ignore_paths_with)
2092			{
2093				// Don't cache calls using the $ignore_paths_with parameter
2094				return $executable_path;
2095			}
2096
2097			$cache[$executable] = $executable_path;
2098		}
2099		if($cache[$executable] == false && phodevi::is_windows() && substr($executable, -4) != '.exe')
2100		{
2101			// See if there is .exe match most likely, e.g. Java, Python, etc.
2102			$cache[$executable] = pts_client::executable_in_path($executable . '.exe');
2103		}
2104
2105		return $cache[$executable];
2106	}
2107	public static function test_for_result_viewer_connection($port)
2108	{
2109		if(is_numeric($port))
2110		{
2111			$dynamic_urls_to_try = array();
2112
2113			// Due to the way PHP web server is handled depending upon if remote allowed,
2114			// both URLs need to be tried
2115			$dynamic_urls_to_try[] = 'http://localhost:' . $port;
2116			$dynamic_urls_to_try[] = 'http://127.0.0.1:' . $port;
2117
2118			foreach($dynamic_urls_to_try as $base_url)
2119			{
2120				if(pts_network::http_get_contents($base_url . '/index.php?PTS', false, false, false, false, 3) == 'PTS')
2121				{
2122					return $base_url;
2123				}
2124			}
2125		}
2126
2127		return false;
2128	}
2129	public static function display_result_view($result_file, $auto_open = false, $prompt_text = null)
2130	{
2131		if(defined('PHOROMATIC_PROCESS'))
2132		{
2133			return false;
2134		}
2135
2136		if(!is_object($result_file))
2137		{
2138			$result_file = new pts_result_file($result_file);
2139		}
2140
2141		if(!phodevi::is_display_server_active())
2142		{
2143			$prompt_text = !empty($prompt_text) ? $prompt_text : 'Do you want to view the test results?';
2144			$txt_results = $auto_open || pts_user_io::prompt_bool_input($prompt_text, true);
2145			if($txt_results)
2146			{
2147				echo pts_result_file_output::result_file_to_text($result_file, pts_client::terminal_width());
2148			}
2149		}
2150		else
2151		{
2152			if(TIME_PTS_LAUNCHED > (time() - 6))
2153			{
2154				// Avoid a race condition on start-up if the dynamic result viewer PHP server isn't yet active
2155				// and running a command like 'refresh-graphs' where you may be viewing a result right away
2156				sleep(5);
2157			}
2158
2159			$ports_to_try = array();
2160			if(pts_client::$web_result_viewer_active)
2161			{
2162				$ports_to_try[] = pts_client::$web_result_viewer_active;
2163			}
2164			if(($restored_port = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'last_web_result_viewer_active_port')) != false && is_numeric($restored_port) && $restored_port > 20)
2165			{
2166				$ports_to_try[] = $restored_port;
2167			}
2168
2169			foreach($ports_to_try as $try_port)
2170			{
2171				if(($base_url = pts_client::test_for_result_viewer_connection($try_port)))
2172				{
2173					pts_client::$has_used_modern_result_viewer = true;
2174					pts_client::$last_result_view_url = $base_url . '/result/' . $result_file->get_identifier();
2175					$length_browser_open = pts_client::display_web_page(pts_client::$last_result_view_url, $prompt_text, true, $auto_open);
2176					return true;
2177				}
2178			}
2179
2180			// Failed to start/find the dynamic result viewer...
2181			trigger_error('Dynamic result viewer not running or inaccessible', E_USER_WARNING);
2182			$prompt_text = !empty($prompt_text) ? $prompt_text : 'Do you want to view the text-based test results?';
2183			$txt_results = $auto_open || pts_user_io::prompt_bool_input($prompt_text, true);
2184			if($txt_results)
2185			{
2186				echo pts_result_file_output::result_file_to_text($result_file, pts_client::terminal_width());
2187			}
2188		}
2189	}
2190	public static function display_web_page($URL, $alt_text = null, $default_open = true, $auto_open = false)
2191	{
2192		if(!phodevi::is_display_server_active() || defined('PHOROMATIC_PROCESS'))
2193		{
2194			return -1;
2195		}
2196
2197		if($auto_open == false)
2198		{
2199			$view_results = pts_user_io::prompt_bool_input(($alt_text == null ? 'Do you want to view the results in your web browser' : $alt_text), $default_open);
2200		}
2201		else
2202		{
2203			$view_results = true;
2204		}
2205
2206		if($view_results)
2207		{
2208			static $browser = null;
2209
2210			if($browser == null)
2211			{
2212				$config_browser = pts_config::read_user_config('PhoronixTestSuite/Options/General/DefaultBrowser', null);
2213
2214				if($config_browser != null && (is_executable($config_browser) || ($config_browser = pts_client::executable_in_path($config_browser))))
2215				{
2216					$browser = $config_browser;
2217				}
2218				else if(phodevi::is_windows())
2219				{
2220					$windows_browsers = array(
2221						'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
2222						'C:\Program Files (x86)\Mozilla Firefox\firefox.exe',
2223						//'C:\Program Files\internet explorer\iexplore.exe'
2224						);
2225
2226					foreach($windows_browsers as $browser_test)
2227					{
2228						if(is_executable($browser_test))
2229						{
2230							$browser = $browser_test;
2231							break;
2232						}
2233					}
2234
2235					if(!empty($browser))
2236					{
2237						$browser = escapeshellarg($browser);
2238					}
2239
2240					if(substr($URL, 0, 1) == '\\')
2241					{
2242						$URL = 'file:///C:' . str_replace('/', '\\', $URL);
2243					}
2244					else if(substr($URL, 0, 2) == 'C:')
2245					{
2246						$URL = 'file:///' . str_replace('//', '/', str_replace('\\', '/', $URL));
2247					}
2248
2249
2250					pts_client::$last_browser_launch_time = time();
2251					$launch_time = microtime(true);
2252					if(empty($browser))
2253					{
2254						// should allow the browser to be opened in Edge
2255						shell_exec('start ' . $URL . '');
2256					}
2257					else
2258					{
2259						shell_exec($browser . ' "' . $URL . '"');
2260					}
2261					pts_client::$last_browser_duration = microtime(true) - $launch_time;
2262					return -1;
2263				}
2264				else
2265				{
2266					$possible_browsers = array('x-www-browser', 'google-chrome', 'chromium', 'firefox', 'mozilla', 'iceweasel', 'konqueror', 'epiphany', 'midori', 'epiphany-browser', 'epiphany', 'falkon', 'qupzilla', 'open', 'xdg-open');
2267
2268					// First try to see if a browser is already running and use that
2269					foreach($possible_browsers as &$b)
2270					{
2271						if(pts_client::is_process_running($b) && ($b = pts_client::executable_in_path($b)))
2272						{
2273							$browser = $b;
2274							break;
2275						}
2276					}
2277
2278					// Otherwise just find any browser available in PATH
2279					if($browser == null)
2280					{
2281						foreach($possible_browsers as &$b)
2282						{
2283							if(($b = pts_client::executable_in_path($b)))
2284							{
2285								$browser = $b;
2286								break;
2287							}
2288						}
2289					}
2290				}
2291			}
2292
2293			if($browser != null)
2294			{
2295				$launch_time = microtime(true);
2296				pts_client::$last_browser_launch_time = time();
2297				shell_exec($browser . ' "' . $URL . '" 2> /dev/null &');
2298
2299				// return how long the browser was opened, useful for trying to see if launched to an existing open browser or process stayed while viewing results
2300				pts_client::$last_browser_duration = microtime(true) - $launch_time;
2301				return pts_client::$last_browser_duration;
2302			}
2303			else
2304			{
2305				echo PHP_EOL . 'No Web Browser Found.' . PHP_EOL;
2306			}
2307		}
2308
2309		return -1;
2310	}
2311	public static function timed_function($function, $function_parameters, $time, $continue_while_true_function = false, $continue_while_true_function_parameters = null)
2312	{
2313		if(($time < 0.5 && $time != -1) || $time > 300)
2314		{
2315			return;
2316		}
2317		if($continue_while_true_function && $continue_while_true_function_parameters === null)
2318		{
2319			$continue_while_true_function_parameters = array();
2320		}
2321
2322		if(function_exists('pcntl_fork') && function_exists('posix_setsid'))
2323		{
2324			$current_pid = function_exists('posix_getpid') ? posix_getpid() : -1;
2325			$pid = pcntl_fork();
2326
2327			if($pid == -1)
2328			{
2329				trigger_error('Could not fork ' . $function . '.', E_USER_ERROR);
2330			}
2331			else if($pid)
2332			{
2333				self::$forked_pids[] = $pid;
2334			}
2335			else
2336			{
2337				posix_setsid();
2338				$loop_continue = true;
2339				while($loop_continue && is_file(PTS_USER_LOCK) && ($continue_while_true_function === true || ($loop_continue = call_user_func_array($continue_while_true_function, $continue_while_true_function_parameters))))
2340				{
2341					call_user_func_array($function, $function_parameters);
2342
2343					if($time > 0)
2344					{
2345						sleep($time);
2346					}
2347					else if($time == -1)
2348					{
2349						$loop_continue = false;
2350					}
2351					if($current_pid != -1 && !is_dir('/proc/' . $current_pid))
2352					{
2353						exit;
2354					}
2355					clearstatcache();
2356				}
2357				if(function_exists('posix_kill'))
2358				{
2359					posix_kill(posix_getpid(), SIGINT);
2360				}
2361				exit(0);
2362			}
2363		}
2364		else
2365		{
2366			if(is_array($function))
2367			{
2368				$function = implode(':', $function);
2369			}
2370
2371			trigger_error('php-pcntl and php-posix must be installed for calling ' . $function . '.', E_USER_ERROR);
2372		}
2373	}
2374	public static function fork($fork_function, $fork_function_parameters = null)
2375	{
2376		if(!is_array($fork_function_parameters))
2377		{
2378			$fork_function_parameters = array($fork_function_parameters);
2379		}
2380
2381		if(function_exists('pcntl_fork'))
2382		{
2383			$current_pid = function_exists('posix_getpid') ? posix_getpid() : -1;
2384			$pid = pcntl_fork();
2385
2386			if($pid == -1)
2387			{
2388				trigger_error('Could not fork ' . $fork_function . '.', E_USER_ERROR);
2389			}
2390			else if($pid)
2391			{
2392				// PARENT
2393				self::$forked_pids[] = $pid;
2394				return true;
2395			}
2396			else
2397			{
2398				// CHILD
2399				// posix_setsid();
2400				call_user_func_array($fork_function, $fork_function_parameters);
2401				if(function_exists('posix_kill'))
2402				{
2403					posix_kill(posix_getpid(), SIGINT);
2404				}
2405				exit(0);
2406			}
2407		}
2408		else
2409		{
2410			// No PCNTL Support
2411			call_user_func_array($fork_function, $fork_function_parameters);
2412		}
2413
2414		return false;
2415	}
2416	public static function code_error_handler($error_code, $error_string, $error_file, $error_line)
2417	{
2418		/*if(!(error_reporting() & $error_code))
2419		{
2420			return;
2421		}*/
2422
2423		switch($error_code)
2424		{
2425			case E_USER_ERROR:
2426				$error_type = 'PROBLEM';
2427				if(pts_client::is_debug_mode() == false)
2428				{
2429					$error_file = null;
2430					$error_line = 0;
2431				}
2432				break;
2433			case E_USER_NOTICE:
2434				if(pts_client::is_debug_mode() == false)
2435				{
2436					return;
2437				}
2438				$error_type = 'NOTICE';
2439				break;
2440			case E_USER_WARNING:
2441				$error_type = 'NOTICE'; // Yes, report warnings as a notice
2442				if(pts_client::is_debug_mode() == false)
2443				{
2444					$error_file = null;
2445					$error_line = 0;
2446				}
2447				break;
2448			case E_ERROR:
2449			case E_PARSE:
2450				$error_type = 'ERROR';
2451				break;
2452			case E_WARNING:
2453			case E_NOTICE:
2454				$error_type = 'NOTICE';
2455				if(($s = strpos($error_string, 'Undefined ')) !== false && ($x = strpos($error_string, ': ', $s)) !== false)
2456				{
2457					$error_string = 'Undefined: ' . substr($error_string, ($x + 2));
2458				}
2459				else if(strpos($error_string, 'Unable to find the socket transport') !== false || strpos($error_string, 'SSL: Connection reset') !== false)
2460				{
2461					$error_string = 'PHP OpenSSL support is needed to handle HTTPS downloads.';
2462					$error_file = null;
2463					$error_line = null;
2464				}
2465				else
2466				{
2467					$ignore_errors = array(
2468						'Name or service not known',
2469						'HTTP request failed',
2470						'fopen',
2471						'fsockopen',
2472						'file_get_contents',
2473						'failed to connect',
2474						'unable to connect',
2475						'directory not empty',
2476						'_lock', // likely multi-process issue, etc for unlinking lock
2477						);
2478
2479					foreach($ignore_errors as $error_check)
2480					{
2481						if(stripos($error_string, $error_check) !== false)
2482						{
2483							return;
2484						}
2485					}
2486				}
2487				break;
2488			default:
2489				$error_type = $error_code;
2490				break;
2491		}
2492
2493		if(pts_client::$pts_logger != false)
2494		{
2495			pts_client::$pts_logger->report_error($error_type, $error_string, $error_file, $error_line);
2496		}
2497
2498		if(pts_client::$display != false)
2499		{
2500			pts_client::$display->triggered_system_error($error_type, $error_string, $error_file, $error_line);
2501		}
2502		else
2503		{
2504			echo PHP_EOL . $error_string;
2505
2506			if($error_file != null && $error_line != null)
2507			{
2508				echo ' in ' . $error_file . ':' . $error_line;
2509			}
2510
2511			echo PHP_EOL;
2512		}
2513
2514		if($error_type == 'ERROR')
2515		{
2516			exit(1);
2517		}
2518	}
2519	public static function set_debug_mode($dmode)
2520	{
2521		self::$debug_mode = ($dmode == true);
2522	}
2523	public static function is_debug_mode()
2524	{
2525		// debug mode for tests
2526		return self::$debug_mode == true;
2527	}
2528	public static function update_download_speed_average($download_size, $elapsed_time)
2529	{
2530		if(self::$download_speed_average_count == -1)
2531		{
2532			self::load_download_speed_averages();
2533		}
2534
2535		$download_speed = floor($download_size / $elapsed_time); // bytes per second
2536
2537		if(self::$download_speed_average_count > 0 && self::$download_speed_average_speed > 0)
2538		{
2539			// bytes per second
2540			self::$download_speed_average_speed = floor(((self::$download_speed_average_speed * self::$download_speed_average_count) + $download_speed) / (self::$download_speed_average_count + 1));
2541			self::$download_speed_average_count++;
2542		}
2543		else
2544		{
2545			self::$download_speed_average_speed = $download_speed;
2546			self::$download_speed_average_count = 1;
2547		}
2548	}
2549	public static function get_average_download_speed()
2550	{
2551		if(self::$download_speed_average_count == -1)
2552		{
2553			self::load_download_speed_averages();
2554		}
2555
2556		return self::$download_speed_average_speed;
2557	}
2558	private static function load_download_speed_averages()
2559	{
2560		self::$download_speed_average_count = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'download_average_count');
2561		self::$download_speed_average_speed = pts_storage_object::read_from_file(PTS_CORE_STORAGE, 'download_average_speed');
2562	}
2563	public static function save_download_speed_averages()
2564	{
2565		pts_storage_object::set_in_file(PTS_CORE_STORAGE, 'download_average_count', self::$download_speed_average_count);
2566		pts_storage_object::set_in_file(PTS_CORE_STORAGE, 'download_average_speed', self::$download_speed_average_speed);
2567	}
2568}
2569
2570// Some extra magic
2571set_error_handler(array('pts_client', 'code_error_handler'));
2572
2573if(PTS_IS_CLIENT && (PTS_IS_DEV_BUILD || pts_client::is_debug_mode()))
2574{
2575	// Enable more verbose error reporting only when PTS is in development with milestone (alpha/beta) releases but no release candidate (r) or gold versions
2576	error_reporting(E_ALL | E_NOTICE | E_STRICT);
2577}
2578
2579?>
2580