<?php

/*
	Phoronix Test Suite
	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
	Copyright (C) 2008 - 2020, Phoronix Media
	Copyright (C) 2008 - 2020, Michael Larabel
	system_monitor.php: System sensor monitoring module for PTS

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// TODO XXX: Port to new phodevi_sensor_monitor interface
class system_monitor extends pts_module_interface
{
	const module_name = 'System Monitor';
	const module_version = '3.4.0';
	const module_description = 'This module contains sensor monitoring support.';
	const module_author = 'Michael Larabel';

	private static $result_identifier = null;
	private static $to_monitor = array();
	private static $monitor_pids = array();
	private static $monitor_test_count = 0;

	private static $individual_test_run_request = null;
	private static $successful_test_run_request = null;
	private static $individual_test_run_offsets = null;
	private static $test_run_tries_offsets = null;

	private static $individual_monitoring = null;
	private static $per_test_try_monitoring = null;

	private static $cgroup_name = 'pts_monitor';			// default name for monitoring cgroup
	private static $cgroup_enabled_controllers = array();

	private static $test_run_try_number = null;
	private static $sensor_monitoring_frequency = 1;
	private static $test_run_timer = 0;
	private static $perf_per_sensor_collection;
	private static $perf_per_sensor = false;

	public static function module_environment_variables()
	{
		return array('MONITOR', 'PERFORMANCE_PER_WATT', 'PERFORMANCE_PER_SENSOR', 'MONITOR_INTERVAL', 'MONITOR_PER_RUN' );
	}

	public static function module_info()
	{
		$info = null;
		$info .= PHP_EOL . 'Monitoring these sensors is as easy as running your normal Phoronix Test Suite commands but at the beginning of the command add: MONITOR=<selected sensors>.  For example, this will monitor the CPU temperature and voltage during tests: '
            . PHP_EOL . PHP_EOL . '    MONITOR=cpu.temp,cpu.voltage phoronix-test-suite benchmark universe'
            . PHP_EOL . PHP_EOL . 'For some of the sensors there is an ability to monitor specific device, e.g. cpu.usage.cpu0 or hdd.read-speed.sda. If the PERFORMANCE_PER_WATT environment variable is set, a performance per Watt graph will also be added, assuming the system\'s power consumption can be monitored. PERFORMANCE_PER_SENSOR= will allow similar behavior but for arbitrary sensors. Below are all of the sensors supported by this version of the Phoronix Test Suite.' 
            . PHP_EOL . PHP_EOL . 'Supported Options:' . PHP_EOL . PHP_EOL;

		foreach(self::monitor_arguments() as $arg)
		{
			$info .= '  - ' . $arg . PHP_EOL;
		}

        $info .= PHP_EOL . 'NOTE: Use the "system-sensors" command to see what sensors are available for monitoring on the system.' . PHP_EOL;

		return $info;
	}

	//
	// General Functions
	//

	public static function __run_manager_setup(&$test_run_manager)
	{
		$test_run_manager->force_results_save();
		//$test_run_manager->disable_dynamic_run_count();
	}
	public static function __pre_run_process(&$test_run_manager)
	{
		self::$result_identifier = $test_run_manager->get_results_identifier();
		self::$individual_monitoring = pts_env::read('MONITOR_INDIVIDUAL') !== '0';
		self::$per_test_try_monitoring = pts_env::read('MONITOR_PER_RUN') === '1';
		self::$to_monitor = array();

		try
		{
			$sensor_parameters = self::prepare_sensor_parameters();
			self::enable_perf_per_sensor($sensor_parameters);
			self::process_sensor_list($sensor_parameters);
			self::create_monitoring_cgroups();
			self::print_monitored_sensors();
			self::set_monitoring_interval();

			// Pad some idling sensor results at the start
			sleep((self::$sensor_monitoring_frequency * 8));

			self::pts_start_monitoring();
		}
		catch(Exception $e)
		{
			echo PHP_EOL . 'Unloading system monitor: ' . $e->getMessage();
			sleep(5);
			return pts_module::MODULE_UNLOAD;
		}

		pts_env::set('FORCE_MIN_DURATION_PER_TEST', 1); // force each test to run at least one minute to ensure sufficient samples
	}
	public static function __pre_test_run($test_run_request)
	{
		self::$individual_test_run_request = clone $test_run_request;

		// Just to pad in some idling into the run process
		sleep(self::$sensor_monitoring_frequency);

		// We're going to run the test for the first time (but will probably repeat it several times).
		self::$test_run_try_number = 0;
		self::save_log_offsets('run');

		self::$test_run_timer = time();
	}
	public static function __test_running($test_process)
	{
		// Put the tested application into proper cgroups as soon as it starts.
		foreach(self::$cgroup_enabled_controllers as $controller)
		{
			$proc_status = proc_get_status($test_process);
			$parent_pid = $proc_status['pid'];
			file_put_contents('/sys/fs/cgroup/' . $controller . '/' . self::$cgroup_name .'/tasks', $parent_pid);
		}

		if(self::$per_test_try_monitoring)
		{
			self::save_log_offsets('try');
		}
	}
	public static function __interim_test_run()
	{
		if(self::$per_test_try_monitoring)
		{
			self::$test_run_try_number++;
			self::save_log_offsets('try');
		}
	}
	public static function __post_test_run_success($test_run_request)
	{
		if(self::$per_test_try_monitoring)
		{
			self::$test_run_try_number++;
			self::save_log_offsets('try');
		}

		self::$successful_test_run_request = clone $test_run_request;
	}
	public static function __post_test_run_process(&$result_file)
	{
		// The self::$test_run_timer to contain how long each individual test run lasted, should anything else past this point want to use the info...
		self::$test_run_timer = time() - self::$test_run_timer;

		// Let the system return to brief idling..
		//sleep(self::$sensor_monitoring_frequency * 8);

		if(!empty(self::$perf_per_sensor))
		{
			self::process_perf_per_sensor($result_file);
		}

		if(self::$individual_monitoring != false && self::$successful_test_run_request)
		{
			foreach(self::$to_monitor as $sensor)
			{
				self::process_test_run_results($sensor, $result_file);
			}
		}

		self::$test_run_tries_offsets = array();
		self::$successful_test_run_request = null;
		self::$individual_test_run_request = null;
		self::$monitor_test_count++;

		// Let the system rest before jumping to next test...
		sleep((self::$sensor_monitoring_frequency * 6));
	}
	public static function __event_results_process(&$test_run_manager)
	{
		self::process_perf_per_sensor_collection($test_run_manager);

		echo PHP_EOL . 'Finishing System Sensor Monitoring Process' . PHP_EOL;

		foreach(self::$to_monitor as $sensor)
		{
			self::process_summary_results($sensor, $test_run_manager);
		}
		echo PHP_EOL;
		foreach(self::$monitor_pids as $pid)
		{
			if(function_exists('posix_kill') && defined('SIGTERM'))
			{
				posix_kill($pid, SIGTERM);
			}
			else if(pts_client::executable_in_path('kill'))
			{
				shell_exec('kill ' . $pid . ' > /dev/null 2>&1');
			}
			else
			{
				// TODO XXX
				continue;
			}

			if(function_exists('pcntl_waitpid'))
			{
				pcntl_waitpid($pid, $status);
			}
		}

		self::$monitor_pids = array();
	}
	public static function __post_run_process()
	{
		foreach(self::$cgroup_enabled_controllers as $controller)
		{
			self::cgroup_remove(self::$cgroup_name, $controller);
		}
	}
	private static function pts_start_monitoring()
	{
		$instant_sensors = array();

		foreach(self::$to_monitor as $sensor)
		{
			$is_instant = $sensor->is_instant();

			if($is_instant === false)
			{
				$pid = pts_module::pts_timed_function('pts_monitor_update', self::$sensor_monitoring_frequency, array(array($sensor)));
				self::$monitor_pids[] = $pid;
			}
			else
			{
				$instant_sensors[] = $sensor;
			}
		}

		if(!empty($instant_sensors))
		{
			$pid = pts_module::pts_timed_function('pts_monitor_update', self::$sensor_monitoring_frequency, array($instant_sensors));
			self::$monitor_pids[] = $pid;
		}
	}

	// Reads value of a single sensor, checks its correctness and saves it to the monitor log.
	public static function pts_monitor_update($sensor_list)
	{
		foreach($sensor_list as $sensor)
		{
			$sensor_value = phodevi::read_sensor($sensor);

			if($sensor_value != -1 && pts_module::is_file('logs/' . phodevi::sensor_object_identifier($sensor)))
			{
				pts_module::save_file('logs/' . phodevi::sensor_object_identifier($sensor), $sensor_value, true);
			}
		}
	}

	private static function parse_monitor_log($log_file, $start_offset = 0, $end_offset = -1)
	{
		$log_f = pts_module::read_file($log_file);
		$line_breaks = explode(PHP_EOL, $log_f);
		$results = array();

		for($i = 0; $i < $start_offset && isset($line_breaks[$i]); $i++)
		{
			unset($line_breaks[$i]);
		}

		foreach($line_breaks as $line_number => $line)
		{
			if($end_offset != -1 && $line_number >= $end_offset)
			{
				break;
			}

			$line = trim($line);

			if(!empty($line) && $line >= 0)
			{
				$results[] = $line;
			}
		}

		if(count($results) > 0 && max($results) == 0)
		{
			$results = array();
		}

		return $results;
	}

	private static function monitor_arguments()
	{
		$args = array('all');

		foreach(phodevi::available_sensors() as $sensor)
		{
			$supported_devices = call_user_func(array($sensor[2], 'get_supported_devices'));

			if(!in_array('all.' . $sensor[0], $args))
			{
				$args[] = 'all.' . $sensor[0];
			}

			$args[] = phodevi::sensor_identifier($sensor);

			if($supported_devices !== NULL)
			{
				$args[] = 'all.' . phodevi::sensor_identifier($sensor);
				foreach($supported_devices as $device)
				{
					$args[] = phodevi::sensor_identifier($sensor) . '.' . $device;
				}
			}

		}

		return $args;
	}

	// Parse environment variable containing parameters of monitored sensors.
	private static function prepare_sensor_parameters()
	{
		$sensor_list = pts_strings::comma_explode(pts_env::read('MONITOR'));

		$to_monitor = array();

		foreach($sensor_list as $sensor)
		{
			$sensor_split = pts_strings::trim_explode('.', $sensor);

			// Set 'all' from the beginning (eg. all.cpu.frequency) as the last
			// element (cpu.frequency.all). As sensor parameters are also supported
			// now, it's handy to mark that we want to include all sensors of specified
			// type (cpu.all) or just all supported parameters of specified sensor
			// (cpu.frequency.all).
			if($sensor_split[0] === 'all')
			{
				$sensor_split[] = 'all';
				array_shift($sensor_split);
			}

			$type = &$sensor_split[0];
			$name = &$sensor_split[1];
			$parameter = &$sensor_split[2];

			if(empty($to_monitor[$type][$name]))
			{
				$to_monitor[$type][$name] = array();
			}

			if($parameter !== NULL)
			{
				$to_monitor[$type][$name][] = $parameter;
			}
		}

		return $to_monitor;
	}

	private static function enable_perf_per_sensor(&$sensor_parameters)
	{
		self::$perf_per_sensor = array();
		if(pts_env::read('PERFORMANCE_PER_WATT'))
		{
			// We need to ensure the system power consumption is being tracked to get performance-per-Watt
			self::$perf_per_sensor[] = array('sys', 'power');
			echo PHP_EOL . 'Provide Performance-Per-Watt Metrics Using: ' . pts_client::cli_just_italic('sys.power')  . PHP_EOL;
		}
		if(pts_env::read('PERFORMANCE_PER_SENSOR'))
		{
			// We need to ensure the system power consumption is being tracked to get performance-per-(arbitrary sensor)
			foreach(explode(',', pts_env::read('PERFORMANCE_PER_SENSOR')) as $s)
			{
				$per_sensor = explode('.', $s);
				if(count($per_sensor) == 2)
				{
					self::$perf_per_sensor[] = $per_sensor;
					echo PHP_EOL . 'Providing Performance-Per-Sensor Metrics Using: ' . pts_client::cli_just_italic($per_sensor[0] . '.' . $per_sensor[1]) . PHP_EOL;
				}
			}
		}

		if(empty(self::$perf_per_sensor))
		{
			return false;
		}

		foreach(self::$perf_per_sensor as $i => $s)
		{
			if(empty($sensor_parameters[$s[0]][$s[1]]))
			{
				$sensor_parameters[$s[0]][$s[1]] = array();
			}

			self::$perf_per_sensor_collection[$i] = array();
		}
	}

	// Create sensor objects basing on the sensor parameter array.
	private static function process_sensor_list(&$sensor_parameters)
	{
		$monitor_all = array_key_exists('all', $sensor_parameters);
		foreach(phodevi::supported_sensors() as $sensor)
		{
			// instantiate sensor class if:
			// a) we want to monitor all the available sensors,
			// b) we want to monitor all the available sensors of the specified type,
			// c) sensor type and name was passed in an environment variable

			// ($sensor[0] is the type, $sensor[1] is the name, $sensor[2] is the class name)

			$sensor_type_exists = array_key_exists($sensor[0], $sensor_parameters);
			$sensor_name_exists = $sensor_type_exists && array_key_exists($sensor[1], $sensor_parameters[$sensor[0]]);
			$monitor_all_of_this_type = $sensor_type_exists && array_key_exists('all', $sensor_parameters[$sensor[0]]);
			$monitor_all_of_this_sensor = $sensor_type_exists && $sensor_name_exists
					&& in_array('all', $sensor_parameters[$sensor[0]][$sensor[1]]);
			$is_cgroup_sensor = $sensor[0] === 'cgroup';

			if(($monitor_all && !$is_cgroup_sensor) || $monitor_all_of_this_type || $sensor_name_exists )
			{
				// in some cases we want to create objects representing every possible device supported by the sensor
				$create_all = $monitor_all || $monitor_all_of_this_type || $monitor_all_of_this_sensor;
				self::create_sensor_instances($sensor, $sensor_parameters, $create_all);
			}
		}

		if(count(self::$to_monitor) == 0)
		{
			throw new Exception('No Supported Sensors Selected To Monitor');
			sleep(2);
		}
	}
	private static function create_sensor_instances(&$sensor, &$sensor_parameters, $create_all)
	{
		if($create_all)
		{
			self::create_all_sensor_instances($sensor);
			return;
		}

		$sensor_instances = $sensor_parameters[$sensor[0]][$sensor[1]];

		// If no instances specified, create one with default parameters.
		if(empty($sensor_instances) )
		{
			self::create_single_sensor_instance($sensor, 0, NULL);
			return;
		}
		// Create objects for all specified instances of the sensor.
		foreach($sensor_instances as $instance => $param)
		{
			self::create_single_sensor_instance($sensor, $instance, $param);
		}
	}

	// Create instances for all of the devices supported by specified sensor.
	private static function create_all_sensor_instances(&$sensor)
	{
		$supported_devices = call_user_func(array($sensor[2], 'get_supported_devices'));
		$instance_no = 0;

		if($supported_devices === NULL)
		{
			self::create_single_sensor_instance($sensor, 0, NULL);
			return;
		}

		foreach($supported_devices as $device)
		{
			self::create_single_sensor_instance($sensor, $instance_no++, $device);
		}
	}

	// Create sensor object if parameters passed to it are correct.
	private static function create_single_sensor_instance($sensor, $instance, $param)
	{
		if($sensor[0] === 'cgroup')
		{
			$cgroup_controller = call_user_func(array($sensor[2], 'get_cgroup_controller'));
			pts_arrays::unique_push(self::$cgroup_enabled_controllers, $cgroup_controller );
			self::cgroup_create(self::$cgroup_name, $cgroup_controller);
			$param = self::$cgroup_name;
		}

		if(call_user_func(array($sensor[2], 'parameter_check'), $param) === true)
		{
			$sensor_object = new $sensor[2]($instance, $param);
			self::$to_monitor[] = $sensor_object;
			pts_module::save_file('logs/' . phodevi::sensor_object_identifier($sensor_object));
		}
	}

	// Create cgroups in all of the needed controllers.
	private static function create_monitoring_cgroups()
	{
		foreach(self::$cgroup_enabled_controllers as $controller)
		{
			self::cgroup_create(self::$cgroup_name, $controller);
		}
	}

	private static function print_monitored_sensors()
	{
		echo PHP_EOL . 'Sensors To Be Logged:';
		foreach(self::$to_monitor as &$sensor)
		{
			echo PHP_EOL . '   - ' . phodevi::sensor_object_name($sensor);
		}
		echo PHP_EOL;

	}
	private static function set_monitoring_interval()
	{
		if(pts_env::read('MONITOR_INTERVAL') != null)
		{
			$proposed_interval = pts_env::read('MONITOR_INTERVAL');
			if(is_numeric($proposed_interval) && $proposed_interval >= 0.5)
			{
				self::$sensor_monitoring_frequency = $proposed_interval;
			}
		}
	}
	private static function cgroup_create($cgroup_name, $cgroup_controller)
	{
		//TODO if we allow custom cgroup names, we will have to add cgroup
		//name checking ("../../../etc" isn't a sane name)

		$sudo_cmd = PTS_CORE_STATIC_PATH . 'root-access.sh ';
		$cgroup_path = '/sys/fs/cgroup/' . $cgroup_controller . '/' . $cgroup_name;
		$return_val = null;

		if(!is_dir($cgroup_path))	// cgroup filesystem doesn't allow to create regular files anyway
		{
			$current_user = exec('whoami');
			$mkdir_cmd = 'mkdir ' . $cgroup_path;
			$chmod_cmd = 'chown ' . $current_user . ' ' . $cgroup_path . '/tasks';
			$command = $sudo_cmd . '"' . $mkdir_cmd . ' && ' . $chmod_cmd . '"';
			exec($command);
		}

		if(!is_writable($cgroup_path . '/tasks'))
		{
			throw new Exception('could not create cgroups');
		}

	}

	private static function cgroup_remove($cgroup_name, $cgroup_controller)
	{
		$sudo_cmd = PTS_CORE_STATIC_PATH . 'root-access.sh ';
		$cgroup_path = '/sys/fs/cgroup/' . $cgroup_controller . '/' . $cgroup_name;

		if(is_dir($cgroup_path))	// cgroup filesystem doesn't allow to create regular files anyway
		{
			$rmdir_cmd = 'rmdir ' . $cgroup_path;
			shell_exec($sudo_cmd . $rmdir_cmd);
		}

		//TODO should probably return some result
	}

	// Saves offsets of sensor log files for the current test or for the specific test try.
	// As sensor monitoring results are saved to the single file during the whole testing process,
	// we need offset information to know where to start drawing chart for the individual test run from.
	private static function save_log_offsets($type)
	{
		foreach(self::$to_monitor as &$sensor)
		{
			$log_f = pts_module::read_file('logs/' . phodevi::sensor_object_identifier($sensor));
			$offset = count(explode(PHP_EOL, $log_f)) - 1;		// as log file ends with an empty line

			if($type === 'try')
			{
				self::$test_run_tries_offsets[phodevi::sensor_object_identifier($sensor)][self::$test_run_try_number] = $offset;
			}
			else if($type === 'run')
			{
				self::$individual_test_run_offsets[phodevi::sensor_object_identifier($sensor)] = $offset;
			}
		}
	}

	private static function process_perf_per_sensor(&$result_file)
	{
		foreach(self::$perf_per_sensor as $i => $s)
		{
			$sensor_results = self::parse_monitor_log('logs/' . phodevi::sensor_identifier($s), (isset(self::$individual_test_run_offsets[phodevi::sensor_identifier($s)]) ? self::$individual_test_run_offsets[phodevi::sensor_identifier($s)] : 0));

			if(count($sensor_results) > 2 && self::$successful_test_run_request)
			{
				// Copy the value each time as if you are directly writing the original data, each succeeding time in the loop the used arguments gets borked
				$original_parent_hash = self::$successful_test_run_request->get_comparison_hash(true, false);
				$test_result = clone self::$successful_test_run_request;
				$unit = 'Watt';

				$res_average = pts_math::arithmetic_mean($sensor_results);
				switch(phodevi::read_sensor_unit($s))
				{
					case 'Milliwatts':
						$res_average = $res_average / 1000;
					case 'Watts':
						break;
					default:
						$unit = phodevi::read_sensor_unit($s);
				}

				if(!empty($unit) && $res_average > 0 && $test_result->test_profile->get_display_format() == 'BAR_GRAPH')
				{
					$test_result->test_profile->set_identifier(null);
					//$test_result->set_used_arguments_description(phodevi::sensor_name('sys.power') . ' Monitor');
					//$test_result->set_used_arguments(phodevi::sensor_name('sys.power') . ' ' . $test_result->get_arguments());
					$test_result->test_result_buffer = new pts_test_result_buffer();
					$test_result->set_parent_hash($original_parent_hash);

					if($test_result->test_profile->get_result_proportion() == 'HIB')
					{
						$test_result->test_profile->set_result_scale($test_result->test_profile->get_result_scale() . ' Per ' . $unit);
						$test_result->test_result_buffer->add_test_result(self::$result_identifier, round($test_result->active->get_result() / $res_average, 3));
						$ro = $result_file->add_result_return_object($test_result);
					}
					else if($test_result->test_profile->get_result_proportion() == 'LIB')
					{
						return; // with below code not rendering nicely
						$test_result->test_profile->set_result_proportion('HIB');
						$test_result->test_profile->set_result_scale('Performance Per ' . $unit);
						$test_result->test_result_buffer->add_test_result(self::$result_identifier, round((1 / $test_result->active->get_result()) / $res_average, 3));
						$ro = $result_file->add_result_return_object($test_result);
					}
					if($ro)
					{
						pts_client::$display->test_run_success_inline($ro);
					}
					self::$perf_per_sensor_collection[$i][] = $test_result->active->get_result();
				}
			}
		}
	}

	// Saves average of perf-per-watt results to the result file.
	private static function process_perf_per_sensor_collection(&$test_run_manager)
	{
		foreach(self::$perf_per_sensor as $i => $s)
		{
			if(is_array(self::$perf_per_sensor_collection[$i]) && count(self::$perf_per_sensor_collection[$i]) > 2)
			{
				// Performance per watt/sensor overall
				$unit = phodevi::read_sensor_unit($s);
				$avg = pts_math::geometric_mean(self::$perf_per_sensor_collection[$i]);
				$test_profile = new pts_test_profile();
				$test_result = new pts_test_result($test_profile);
				$test_result->test_profile->set_test_title('Meta Performance Per ' . $unit);
				$test_result->test_profile->set_identifier(null);
				$test_result->test_profile->set_version(null);
				$test_result->test_profile->set_result_proportion(null);
				$test_result->test_profile->set_display_format('BAR_GRAPH');
				$test_result->test_profile->set_result_scale('Performance Per ' . $unit);
				$test_result->test_profile->set_result_proportion('HIB');
				$test_result->set_used_arguments_description('Performance Per ' . $unit);
				$test_result->set_used_arguments('Per-Per-' . $unit);
				$test_result->test_result_buffer = new pts_test_result_buffer();
				$test_result->test_result_buffer->add_test_result(self::$result_identifier, pts_math::set_precision($avg, 4));
				$test_run_manager->result_file->add_result($test_result);
			}
		}
	}
	private static function process_test_run_results(&$sensor, &$result_file)
	{
		$result_buffer = new pts_test_result_buffer();

		if(self::$per_test_try_monitoring)
		{
			self::prepare_per_try_results($sensor, $result_buffer);
		}
		else
		{
			$sensor_results = self::parse_monitor_log('logs/' . phodevi::sensor_object_identifier($sensor),
								self::$individual_test_run_offsets[phodevi::sensor_object_identifier($sensor)]);

			if(count($sensor_results) > 6)
			{
				$result_buffer->add_test_result(self::$result_identifier, implode(',', $sensor_results), implode(',', $sensor_results));
			}
		}

		if($result_buffer->get_count() == 0)
		{
			return;
		}

		self::write_test_run_results($result_buffer, $result_file, $sensor);
		self::$individual_test_run_offsets[phodevi::sensor_object_identifier($sensor)] = array();
		self::$test_run_tries_offsets[phodevi::sensor_object_identifier($sensor)] = array();
	}
	private static function write_test_run_results(&$result_buffer, &$result_file, &$sensor)
	{
		// TODO result count checks should probably be done before cloning the test_result
		// Copy the value each time as if you are directly writing the original data, each succeeding time in the loop the used arguments gets borked
		if(!is_object(self::$individual_test_run_request))
			return;

		$test_result = clone self::$individual_test_run_request;
		$test_result->set_parent_hash(self::$successful_test_run_request->get_comparison_hash(true, false));

		if(pts_module_manager::is_module_attached('matisk'))
		{
			// TODO find some better way than adding a number to distinguish the results between the MATISK runs
			$arguments_description = phodevi::sensor_object_name($sensor) . ' Monitor (test ' . count($result_file->get_systems()) . ')';
			$arguments_try_description = phodevi::sensor_object_name($sensor) . ' Per Test Try Monitor (test ' . count($result_file->get_systems()) . ')';
		}
		else
		{
			$arguments_description = phodevi::sensor_object_name($sensor) . ' Monitor';
			$arguments_try_description = phodevi::sensor_object_name($sensor) . ' Per Test Try Monitor';
		}

		$scale = phodevi::read_sensor_object_unit($sensor);
		$test_result->test_profile->set_identifier(null);
		$test_result->test_profile->set_result_proportion(stripos($scale, 'hertz') === false && stripos($scale, 'hz') === false ? 'LIB' : 'HIB');
		$test_result->test_profile->set_display_format('LINE_GRAPH');
		$test_result->test_profile->set_result_scale($scale);
		$test_result->set_used_arguments_description($arguments_description);
		$test_result->set_used_arguments(phodevi::sensor_object_name($sensor) . ' ' . $test_result->get_arguments());
		$test_result->test_result_buffer = $result_buffer;

		if(self::$per_test_try_monitoring && $result_buffer->get_count() > 1)
		{
			$test_result->set_used_arguments_description($arguments_try_description);
		}

		$ro = $result_file->add_result_return_object($test_result);
		if($ro)
		{
			pts_client::$display->test_run_success_inline($ro);
		}
	}
	private static function prepare_per_try_results(&$sensor, &$result_buffer)
	{
		$sensor_offsets = self::$test_run_tries_offsets[phodevi::sensor_object_identifier($sensor)];

		for($try_number = 1; $try_number <= self::$test_run_try_number; $try_number++)
		{
			$start_offset = $sensor_offsets[$try_number - 1];
			$end_offset = $sensor_offsets[$try_number];

			$sensor_results = self::parse_monitor_log('logs/' . phodevi::sensor_object_identifier($sensor),
								$start_offset, $end_offset);

			if(count($sensor_results) > 2)
			{
				$result_identifier = self::$result_identifier . " (try " . ($try_number) . ")";
				$result_value = implode(',', $sensor_results);
				$result_buffer->add_test_result($result_identifier, $result_value, $result_value);
			}
		}
	}
	// Generates summary result (covering all test runs) for specified sensor and adds it to the result file.
	private static function process_summary_results(&$sensor, &$test_run_manager)
	{
		$sensor_results = self::parse_monitor_log('logs/' . phodevi::sensor_object_identifier($sensor));
		pts_module::remove_file('logs/' . phodevi::sensor_object_identifier($sensor));

		if(count($sensor_results) > 2 && self::$monitor_test_count > 1)
		{
			$test_profile = new pts_test_profile();
			$test_result = new pts_test_result($test_profile);

			$test_result->test_profile->set_test_title(phodevi::sensor_object_name($sensor) . ' Monitor');
			$test_result->test_profile->set_identifier(null);
			$test_result->test_profile->set_version(null);
			$test_result->test_profile->set_result_proportion(null);
			$test_result->test_profile->set_display_format('LINE_GRAPH');
			$test_result->test_profile->set_result_scale(phodevi::read_sensor_object_unit($sensor));
			$test_result->set_used_arguments_description('Phoronix Test Suite System Monitoring');
			$test_result->set_used_arguments(phodevi::sensor_object_identifier($sensor));
			$test_result->test_result_buffer = new pts_test_result_buffer();
			$test_result->test_result_buffer->add_test_result(self::$result_identifier, implode(',', $sensor_results), implode(',', $sensor_results), implode(',', $sensor_results), implode(',', $sensor_results));
			$ro = $test_run_manager->result_file->add_result_return_object($test_result);
			if($ro)
			{
				pts_client::$display->test_run_success_inline($ro);
			}
		}
	}

}

?>
