from parallels.plesk.hosting_analyser import messages
import logging
import datetime
from xml.etree import ElementTree
from collections import OrderedDict

import os
import threading
import time
from parallels.core.utils.migrator_utils import create_rsync_command, normalize_domain_name
from parallels.core.connections.source_server import SourceServer
from parallels.core.connections.target_servers import TargetServer
from parallels.plesk.source.plesk.server import PleskSourceServer
from parallels.plesk.connections.target_connections import PleskTargetConnections
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.core.utils.yaml_utils import read_yaml, write_yaml
from parallels.core.registry import Registry
from parallels.core.utils.config_utils import ConfigSection
from parallels.core.utils import windows_utils
logger = logging.getLogger(__name__)

# Analysis estimations
UNKNOWN = frozenset([0])
CRITICAL = frozenset([1])
OK = frozenset([2])
ANY = frozenset([0, 1, 2])

analyze_lock = threading.Lock()
result_file_lock = threading.Lock()


# Print helper
def get_bytes_with_suffix(num, prefix='', suffix='B'):
	"""
	:type num: int
	:type prefix: basestring
	:type suffix: basestring
	:rtype: basestring
	"""
	all_prefixes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
	prefixes = all_prefixes[all_prefixes.index(prefix):]
	for unit in prefixes:
		if abs(num) < 1024.0:
			return "%3.1f %s%s" % (num, unit, suffix)
		num /= 1024.0
	return "%.1f %s%s" % (num, 'Y', suffix)


# Print helper
def get_time_duration_for_human(seconds):
	"""
	:type seconds: int
	:rtype: basestring
	"""
	return time.strftime('%M:%S', time.gmtime(seconds))


def get_unix_strategies():
	"""
	:rtype: OrderedDict
	"""
	# TODO: extend this after collecting enough experimental data
	return OrderedDict([
		##############################################
		# order: (CPU, RAM, HDD, NET)
		##############################################
		# without ANY
		##############################################

		##############################################
		# with 1 ANY
		##############################################

		##############################################
		# with 2 ANY
		##############################################
		((UNKNOWN, CRITICAL, ANY, ANY), {'compression': True, 'msg': messages.CPU_UNKNOWN_RAM_CRITICAL_USE_COMPRESSION}),
		((UNKNOWN, OK, ANY, ANY), {'compression': True, 'msg': messages.CPU_UNKNOWN_RAM_OK_USE_COMPRESSION}),
		##############################################
		# with 3 ANY
		##############################################
		((CRITICAL, ANY, ANY, ANY), {'compression': False, 'msg': messages.CPU_CRITICAL_SKIP_COMPRESSION}),
		((ANY, ANY, CRITICAL, ANY), {'compression': False, 'msg': messages.HDD_CRITICAL_SKIP_COMPRESSION}),
		((ANY, ANY, ANY, CRITICAL), {'compression': True, 'msg': messages.NET_CRITICAL_USE_COMPRESSION}),
		((ANY, ANY, ANY, OK), {'compression': True, 'msg': 'NET = OK: use compression'}),
		##############################################
		# with 4 ANY: Default strategy
		##############################################
		((ANY, ANY, ANY, ANY), {'compression': True, 'msg': messages.DEFAULT_STRATEGY_USE_COMPRESSION}),
	])



def get_windows_strategies():
	"""
	:rtype: OrderedDict
	"""
	return OrderedDict([
		##############################################
		# order: (CPU, RAM, HDD, NET)
		##############################################
		# without ANY
		##############################################

		##############################################
		# with 1 ANY
		##############################################

		##############################################
		# with 2 ANY
		##############################################

		##############################################
		# with 3 ANY
		##############################################
		((ANY, ANY, ANY, CRITICAL), {'compression': True, 'msg': messages.NET_CRITICAL_USE_COMPRESSION_MSG}),
		##############################################
		# with 4 ANY: Default strategy
		##############################################
		((ANY, ANY, ANY, ANY), {'compression': False, 'msg': messages.DEFAULT_STRATEGY_SKIP_COMPRESSION}),
	])


def get_coefficients_for_unix():
	"""
	:rtype: list
	"""
	# (k/bin/, k/txt/, k/unk/, k/hdd/, k1/compr/, k2/compr/, compression_level)
	return [(0.952927995773256, 0.289034361542407, 0.620981178657831, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, 3),
			(0.951531607401822, 0.256572015679910, 0.604051811540866, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, None),
			(0.949659735711172, 0.253363634140486, 0.601511684925829, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, 9)]


def get_coefficients_for_windows():
	"""
	:rtype: list
	"""
	return get_coefficients_for_unix()


class HostingAnalyser():
	"""Hosting_analyser calculates the most effective content transfer strategy according to content & environment
	analysis and is used by 'copy-content' action. It detects if data compression is needed and have 2 algorithms
	for calculation:
	- "Matrix of Strategies". Based on knowledge database with combination of environment conditions only.
	- "Math Analysis" [used by default]. Based on mathematical analysis of environment & content conditions.

	Main public methods of Hosting_analyser:

	- analyse() is used for first analysis.	It fetches first content & environment reports. Average time execution
	~8 min.

	- get_webspace_actual_transfer_strategy() is used for refreshing environment reports only (every 15 minutes
	by default) and returns the most effective transfer strategy for the current conditions. Average time execution
	~20 sec. This method requires at least one execution of analyse().
	"""
	def __init__(self, migrator_server):
		"""
		:type migrator_server: parallels.core.connections.migrator_server.MigratorServer
		"""
		# Test file sizes for network speed approximation [MBytes]
		# 1 = 0-10Mbps | 3 = 10Mbps-33Mbps | 6 = 33Mbps-66Mbps | 12 = 66Mpbs-100Mpbs | 24 = 100Mbps-150Mbps
		self.sizes = [1, 3, 6, 12, 24, 48]
		# Amount of estimations for each test file size
		self.network_estimation_attempts_for_steps = 10
		self.remote_xml_report_filename = 'summary.xml'
		self.remote_agent_report_folder_name = 'hosting_analysis_report'  # hosting analyser agent foldername with report
		self.local_dst_plesk_server_report_filename = 'destination-plesk.xml'
		self.agent_filename = 'hosting_analyser_agent.py'
		self.network_analysis_report_filename = '_rsync_network_report'
		self.migration_session_dir = migrator_server.session_dir()
		self.local_analyser_data_folder_path = os.path.join(self.migration_session_dir, 'hosting_analysis_reports')

		if not os.path.exists(self.local_analyser_data_folder_path):
			os.mkdir(self.local_analyser_data_folder_path)

		self.local_agent_full_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.agent_filename)
		for size in self.sizes:
			test_file = self._get_extras_file_path(self._get_network_test_filename(size))
			if not os.path.exists(test_file):
				with open(test_file, "wb") as fp:
					for i in xrange(size):
						fp.write(self._get_random_string())

	def _get_analyser_session_file_path(self, filename):
		"""
		:type filename: basestring
		:rtype: basestring
		"""
		return os.path.join(self.local_analyser_data_folder_path, filename)

	def _get_extras_file_path(self, filename):
		"""
		:type filename: basestring
		:rtype: basestring
		"""
		return os.path.join(self.migration_session_dir, filename)

	def _get_network_test_filename(self, size):
		"""
		:type size: int
		:rtype: basestring
		"""
		return "_rsync_net_test_file_%d" % size

	def _is_server_source(self, server):
		"""
		:type server: object
		:rtype: bool
		"""
		return True if isinstance(server, SourceServer) else False

	def _is_server_target(self, server):
		"""
		:type server: object
		:rtype: bool
		"""
		return True if isinstance(server, TargetServer) else False

	def _is_server_supported(self, server):
		"""
		:type server: object
		:rtype: bool
		"""
		if self._is_server_source(server):
			return True if isinstance(server, PleskSourceServer) else False
		elif self._is_server_target(server):
			return True if isinstance(server, PleskTargetServer) else False
		else:
			return False

	@staticmethod
	def _is_server_target_connections_supported(target_connections):
		"""
		:type target_connections: parallels.core.connections.target_connections.TargetConnections
		:rtype: bool
		"""
		return True if isinstance(target_connections, PleskTargetConnections) else False

	@staticmethod
	def _get_random_string(length=1048576):
		"""
		:type length: int
		:rtype: basestring
		"""
		# default = 1MB
		# Random/identical file content does not affect network transfer test
		# Random/identical file content affects HDD read/write estimation test
		# This generator method is used only for network speed estimation, it's faster than "real" random generator
		return ''.join(['1' for n in xrange(length)])

	# local_mgr_server:./migration-session/hosting_analysis_reports/_tmp_file_1
	def _create_temp_file(self, filename, content):
		"""
		:type filename: basestring
		:type content: basestring
		"""
		file_full_path = self._get_analyser_session_file_path(filename)
		if os.path.exists(file_full_path) and os.path.isfile(file_full_path):
			os.remove(file_full_path)
		with open(file_full_path, "wb") as fp:
			fp.write(content)

	def _clear_existed_data(self):
		try:
			for root, dirs, files in os.walk(self.local_analyser_data_folder_path):
				for name in files:
					os.remove(os.path.join(root, name))
		except:
			logger.warning(messages.UNABLE_TO_CLEAN_UP_FOLDER % root)

	def _get_python_bin(self, server):
		"""
		:type server: parallels.core.connections.source_server.SourceServer | parallels.core.connections.target_servers.TargetServer
		:rtype: basestring
		"""
		python_bin = server.python_bin
		if python_bin is not None:
			logger.debug(messages.DETECTED_PYTHON_BIN.format(path=python_bin))
		return python_bin

	@staticmethod
	def _prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file):
		"""
		:type destination_runner: parallels.core.run_command.BaseRunner
		:type remote_dst_net_test_file: basestring
		"""
		logger.debug(messages.PREPARING_TARGET_SERVER_FOR_NETWORK_ANALYSIS)
		try:
			destination_runner.remove_file(remote_dst_net_test_file)
			logger.debug(messages.TARGET_SERVER_IS_READY_FOR_NETWORK_ANALYSIS)
		except:
			logger.debug(messages.TARGET_SERVER_IS_READY_FOR_ANALYSIS)

	def _get_network_speed_estimations_via_rsync_linux_nodes(
			self, key_pathname, source_runner, source_server, source_node_test_transfer_file_full_path,
			remote_dst_net_test_file, destination_server, size
	):
		"""
		:type key_pathname: basestring
		:type source_runner: parallels.core.run_command.BaseRunner
		:type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
		:type source_node_test_transfer_file_full_path: basestring
		:type remote_dst_net_test_file: basestring
		:type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
		:type size: int
		:rtype: list[float]
		"""
		net_results = []
		rsync_cmd, args = create_rsync_command(
			key_pathname=key_pathname, source_runner=source_runner, source_user=source_server.user(),
			source_ip=source_server.ip(), source_filepath=source_node_test_transfer_file_full_path,
			target_filepath=remote_dst_net_test_file, rsync_additional_args=['-v'],
			source_port=source_server.settings().ssh_auth.port
		)
		for _ in xrange(self.network_estimation_attempts_for_steps):
			with destination_server.runner() as destination_runner:
				self._prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file)
				exit_code, stdout, stderr = destination_runner.run_unchecked(rsync_cmd, args)
			if exit_code == 0:
				net_results.append(self._get_network_speed_from_rsync_report(stdout))
			else:
				logger.debug(messages.ERROR_WHILE_TRANSFERRING_TEST_FILE % size)
				# If we could not copy the file across the network 5 times, we believe that the network is unstable and
				# the net speed is equal to 0
				return [0]
		return net_results

	def _get_network_speed_estimations_via_rsync_windows_nodes(
		self, destination_server, remote_dst_net_test_file, source_server,
		source_node_test_transfer_file_full_path, size
	):
		"""
		:type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
		:type remote_dst_net_test_file: basestring
		:type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
		:type source_node_test_transfer_file_full_path: basestring
		:type size: int
		:rtype: list[float]
		"""
		net_results = []
		for _ in xrange(self.network_estimation_attempts_for_steps):
			with destination_server.runner() as destination_runner:
				self._prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file)
				global_context = Registry.get_instance().get_context()
				rsync = global_context.get_rsync(source_server, destination_server, source_server.ip)
				try:
					stdout = rsync.sync(
						source_path='migrator/%s' % source_node_test_transfer_file_full_path,
						target_path=windows_utils.convert_path_to_cygwin(remote_dst_net_test_file),
						exclude=None, rsync_additional_args=['-v']
					)
					net_results.append(self._get_network_speed_from_rsync_report(stdout))
				except:
					logger.debug(messages.ERROR_OCCURRED_WHILE_TRANSFERRING_TEST_FILE % size)
					# If we could not copy the file across the network 5 times, we believe that the network is unstable and
					# the net speed is equal to 0
					return [0]
		return net_results

	def _get_network_speed_analysis(self, destination_server, source_server, key_pathname=None):
		"""
		:type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
		:type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
		:type key_pathname: basestring | None
		"""
		logger.info(messages.ANALYSING_NETWORK_SPEED % source_server.node_id)
		max_estimated_speed = 0
		cur_estimated_speed = 0
		with source_server.runner() as source_runner:
			for size in self.sizes:
				logger.debug(messages.DEBUG_CALCULATING_NET_SPEED, size, max_estimated_speed, cur_estimated_speed)
				# If new speed value is more than 15% bigger - re-analyse with bigger test-file
				if cur_estimated_speed >= 1.15 * max_estimated_speed:
					filename = self._get_network_test_filename(size)
					# remote_src_server:/tmp/_rsync_net_test_file_12
					source_node_test_transfer_file_full_path = source_server.get_session_file_path(filename)
					# remote_dst_server:/tmp/_rsync_net_test_file_12
					remote_dst_net_test_file = destination_server.get_session_file_path(filename)
					if not source_runner.file_exists(source_node_test_transfer_file_full_path):
						source_runner.upload_file(
							# local_mgr_server:/opt/panel-migrator/thirdparties/python/lib/python2.7/site-packages/parallels/hosting_analyser/extras/_rsync_net_test_file_12
							self._get_extras_file_path(self._get_network_test_filename(size)),
							# remote_src_server:/tmp/_rsync_net_test_file_20
							source_node_test_transfer_file_full_path
						)

					if destination_server.is_windows():
						net_results = self._get_network_speed_estimations_via_rsync_windows_nodes(
							destination_server, remote_dst_net_test_file, source_server,
							source_node_test_transfer_file_full_path, size
						)
					else:
						net_results = self._get_network_speed_estimations_via_rsync_linux_nodes(
							key_pathname, source_runner, source_server, source_node_test_transfer_file_full_path,
							remote_dst_net_test_file, destination_server, size
						)

					average_speed = (max(net_results) + min(net_results)) / 2
					logger.debug(messages.DEBUG_NET_SPEED_RESULTS, size, str(net_results), average_speed)
					# Update max speed & save current speed
					if cur_estimated_speed > 0 and cur_estimated_speed > max_estimated_speed:
						max_estimated_speed = cur_estimated_speed
					cur_estimated_speed = average_speed
				# Peak network speed found, last value - max
				elif cur_estimated_speed > max_estimated_speed:
					max_estimated_speed = cur_estimated_speed
				# Peak network speed found, last value - not max
				else:
					pass
				logger.debug(messages.DEBUG_NET_SPEED_MAX, size, cur_estimated_speed)
			# save last estimation result if is the biggest
			if cur_estimated_speed > max_estimated_speed:
				max_estimated_speed = cur_estimated_speed
			logger.debug(messages.DEBUG_NET_SPEED_ESTIMATION_FINISHED, max_estimated_speed)
		# fixme: [workaround] parallels.core.run_command.SSHRunner.upload_file_content() - Not Implemented
		# local_mgr_server:./migration-session/hosting_analysis_reports/_rsync_network_report
		self._create_temp_file(self.network_analysis_report_filename, str(max_estimated_speed))
		source_runner.upload_file(
			# local_mgr_server:./migration-session/hosting_analysis_reports/_rsync_network_report
			self._get_analyser_session_file_path(self.network_analysis_report_filename),
			# remote_src_server:/tmp/_rsync_network_report
			source_server.get_session_file_path(self.network_analysis_report_filename)
		)
		logger.info(u"Completed")

	def _analyse_network_speed_via_rsync(self, destination_server, source_servers, ssh_key_pool):
		"""
		:type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
		:type source_servers: dict[basestring, parallels.plesk.source.plesk.server.PleskSourceServer]
		:type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
		"""
		# Linux -> Linux
		if not destination_server.is_windows():
			for source_server in source_servers.itervalues():
				if source_server.is_windows():
					logger.debug(messages.NETWORK_SPEED_ANALYSIS_WAS_SKIPPED)
					continue
				key_pathname = ssh_key_pool.get(source_server, destination_server).key_pathname
				self._get_network_speed_analysis(destination_server, source_server, key_pathname)
		# Windows -> Windows
		else:
			for source_server in source_servers.itervalues():
				if not source_server.is_windows():
					logger.debug(messages.NETWORK_SPEED_ANALYSIS_WAS_SKIPPED)
					continue
				self._get_network_speed_analysis(destination_server, source_server)

	@staticmethod
	def _get_network_speed_from_rsync_report(text):
		"""
		:type text: basestring
		:rtype: int | float
		"""
		result = 0
		try:
			text_elements = text.split()
			if len(text_elements) > 12 and text_elements[5] == 'sent' and text_elements[10] == 'bytes':
				result = float(text_elements[11].replace(',', ''))
		except:
			logger.debug(messages.ERROR_WHILE_READING_RSYNC_OUTPUT)
		return result

	def _get_update_source_nodes_analysis(self, connections, ssh_key_pool):
		"""
		:type connections: parallels.plesk.source.plesk.connections.PleskMigratorConnections
		:type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
		"""
		# Re-estimate network speed
		target_server = connections.target.plesk_server
		source_servers = {}
		for source_server_id in connections.get_information_servers().keys():
			source_servers[source_server_id] = connections.get_source_node(source_server_id)
		self._analyse_network_speed_via_rsync(target_server, source_servers, ssh_key_pool)
		# Re-launch 'hosting_analyser_agent'
		for server in source_servers.values():
			try:
				with server.runner() as runner:
					agent_dst_full_path = server.get_session_file_path(self.agent_filename)
					if not runner.file_exists(agent_dst_full_path):
						runner.upload_file(self.local_agent_full_path, agent_dst_full_path)
					args = ' "-f" "-d" "%s" "-n" "%s" "-c"' % (
						server.vhosts_dir, server.get_session_file_path(self.network_analysis_report_filename)
					)
					src = os.path.join(
						server.vhosts_dir, self.remote_agent_report_folder_name, self.remote_xml_report_filename
					)
					dst = self._get_analyser_session_file_path('update_%s.xml' % server.node_id)
					logger.info(messages.LOG_ANALYSING_SOURCE_NODE, server.node_id)
					python_bin = self._get_python_bin(server)
					if python_bin is None:
						logger.warning(
							messages.HOSTING_ANALYZER_NO_PYTHON_AVAILABLE.format(server=server.description())
						)
						return
					cmd = '"%s" "%s" %s' % (python_bin, agent_dst_full_path, args)
					script_exec_result = runner.sh_unchecked(cmd)
					# pick up XML report
					if script_exec_result[0] == 0:
						with result_file_lock:
							runner.get_file(src, dst)
						logger.info(u"Completed")
					else:
						logger.warning(u"Failed")
			except Exception as e:
				logger.warning(messages.INTERNAL_ERROR_UNABLE_TO_REANALYSE_SOURCE)
				logger.debug(u"Exception: ", exc_info=e)

	def _get_node_analysis(self, server):
		"""
		:type server: parallels.plesk.source.plesk.server.PleskSourceServer | parallels.plesk.connections.target_server.PleskTargetServer
		"""
		try:
			with server.runner() as runner:
				# remote_server:/tmp/hosting_analyser_agent.py
				agent_dst_full_path = server.get_session_file_path(self.agent_filename)
				runner.upload_file(self.local_agent_full_path, agent_dst_full_path)
				args = ' "-f" '
				# source
				if self._is_server_source(server):
					# remote_src_server:/var/www/vhosts
					args += ' "-d" "%s" "-n" "%s" ' % (server.vhosts_dir, server.get_session_file_path(self.network_analysis_report_filename))
					# remote_src_server:/var/www/vhosts/hosting_analysis_report/summary.xml
					src = os.path.join(server.vhosts_dir, self.remote_agent_report_folder_name, self.remote_xml_report_filename)
					# local_mgr_server:./migration-session/hosting_analysis_reports/pfu5.xml
					dst = self._get_analyser_session_file_path('%s.xml' % server.node_id)
					logger.info(u"Analysing source node '%s'", server.node_id)
				# destination
				else:
					args += ' "-c" '
					# remote_dst_server:/tmp/hosting_analysis_report/summary.xml
					src = os.path.join(server.session_dir(), self.remote_agent_report_folder_name, self.remote_xml_report_filename)
					# local_mgr_server:./migration-session/hosting_analysis_reports/destination-plesk.xml
					dst = self._get_analyser_session_file_path(self.local_dst_plesk_server_report_filename)
					logger.info(u"Analysing target node")
				# SRV: 'python' '/tmp/hosting_analyser_agent.py' '-d' '/var/www/vhosts' '-n' '/tmp/_rsync_network_report'
				# DST: 'python' '/tmp/hosting_analyser_agent.py' '-c'
				python_bin = self._get_python_bin(server)
				if python_bin is None:
					logger.warning(messages.HOSTING_ANALYZER_NO_PYTHON_AVAILABLE.format(server=server.description()))
					return
				cmd = '"%s" "%s" %s' % (python_bin, agent_dst_full_path, args)
				script_exec_result = runner.sh_unchecked(cmd)
				# pick up XML report
				if script_exec_result[0] == 0:
					runner.get_file(src, dst)
					logger.info(u"Completed")
				else:
					logger.warning(u"Failed")
		except Exception as e:
			logger.warning(messages.INTERNAL_ERROR_UNABLE_TO_ANALYSE_SN)
			logger.debug(u"Exception: ", exc_info=e)

	# Check nodes (remote_sources, remote_destination) types
	def is_nodes_type_supported(self, connections):
		"""
		:type connections: parallels.core.connections.connections.Connections
		"""
		target_connections = connections.target
		source_servers = {}
		for source_server_id in connections.get_information_servers().keys():
			source_servers[source_server_id] = connections.get_source_node(source_server_id)
		try:
			if not self._is_server_target_connections_supported(target_connections):
				return False
			for source_server in source_servers.itervalues():
				if not self._is_server_supported(source_server):
					return False
			return True
		except Exception as e:
			logger.debug(u"Exception: ", exc_info=e)
			return False

	def get_webspace_actual_transfer_strategy(self, subscription):
		"""
		:type subscription: parallels.core.migrated_subscription.MigratedSubscription
		:rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
		"""
		global_context = Registry.get_instance().get_context()
		if self.is_nodes_type_supported(global_context.conn):
			logger.debug(messages.DETERMINING_SUITABLE_CONTENT_TRANSFER_STRATEGY)
			source_server = subscription.web_source_server
			if self.has_input_data(source_server.node_id):
				global_section = ConfigSection(global_context.config, 'GLOBAL')
				use_math_analyse = global_section.get('use-math-analyse', 'true').lower() == 'true'
				transfer_strategy = self.get_updated_webspace_transfer_strategy(source_server.node_id, subscription.name_idn, use_math_analyse)
				if transfer_strategy is not None:
					logger.debug(transfer_strategy.message)
					return transfer_strategy
			else:
				logger.info(
					messages.INFO_FOR_HOSTING_ENVIRONMENT_ANALYSIS_NOT_AVAILABLE)
		return TransferStrategy(subscription.name_idn)

	def analyse(self, connections, ssh_key_pool):
		"""
		:type connections: parallels.plesk.source.plesk.connections.PleskMigratorConnections
		:type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
		"""
		# clear analyser data folder before new analysis
		self._clear_existed_data()
		# Nodes initialisation
		target_server = connections.target.plesk_server
		source_servers = {}
		for source_server_id in connections.get_information_servers().keys():
			source_servers[source_server_id] = connections.get_source_node(source_server_id)
		# Step 1: Network speed analysis between SOURCES and DESTINATION
		self._analyse_network_speed_via_rsync(target_server, source_servers, ssh_key_pool)
		# Step 2: Source nodes analysing
		for source_server in source_servers.itervalues():  # Handling SOURCE servers
			self._get_node_analysis(source_server)

	def has_input_data(self, source_node_id):
		"""
		:type source_node_id: basestring
		:rtype: bool
		"""
		# local_mgr_server:./migration-session/hosting_analysis_reports/pfu1.xml
		input_xml_file = self._get_analyser_session_file_path("%s.xml" % source_node_id)
		return True if os.path.exists(input_xml_file) and os.path.isfile(input_xml_file) else False

	def get_updated_webspace_transfer_strategy(self, source_node_id, webspace_name, use_math_analyse=True):
		"""
		:type source_node_id: basestring
		:type webspace_name: basestring
		:type use_math_analyse: bool
		:rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
		"""
		global_context = Registry.get_instance().get_context()
		global_section = ConfigSection(global_context.config, 'GLOBAL')
		hosting_analysis_ttl = int(global_section.get('hosting-analysis-ttl', 900))
		last_hosting_analysis_time = self._get_last_hosting_analysis_time(global_context)
		# Update environment input data if needed and possible
		if last_hosting_analysis_time is None or (datetime.datetime.now() - last_hosting_analysis_time).seconds > hosting_analysis_ttl:
			result = analyze_lock.acquire(False)
			if result:
				try:
					logger.debug(messages.UPDATING_RESULTS_OF_SOURCE_NODES_ANALYSIS)
					self._get_update_source_nodes_analysis(global_context.conn, global_context.ssh_key_pool)
					self._set_last_hosting_analysis_time(global_context, datetime.datetime.now())
				except Exception as e:
					logger.warning(messages.UNABLE_TO_UPDATE_ENVIRONMENT_ESTIMATION_FOR_SOURCE)
					logger.debug(messages.LOG_EXCEPTION, exc_info=e)
				finally:
					analyze_lock.release()
		transfer_strategy_calculator = TransferStrategyCalculator()
		# local_mgr_server:./migration-session/hosting_analysis_reports/pfu1.xml
		input_xml_file = self._get_analyser_session_file_path("%s.xml" % source_node_id)
		update_input_xml_file = self._get_analyser_session_file_path("update_%s.xml" % source_node_id)
		# Read startup "env" + "content" input data, and update "env" input data if it is available
		try:
			transfer_strategy_calculator.load_data(input_xml_file)
			if os.path.exists(update_input_xml_file) and os.path.isfile(update_input_xml_file):
				transfer_strategy_calculator.load_data(update_input_xml_file)
		except Exception as e:
			logger.warning(messages.UNABLE_TO_LOAD_INPUT_DATA_FOR_STRATEGY_CALCULATION)
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			return None
		if use_math_analyse:
			# using math analysis, ~10% of inaccuracy
			return transfer_strategy_calculator.get_math_analyse(webspace_name)
		else:
			# using "matrix of strategies" knowledge base
			return transfer_strategy_calculator.get_known_strategy(webspace_name)

	@staticmethod
	def _get_last_hosting_analysis_time(global_context):
		"""
		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:rtype: datetime.datetime
		"""
		return read_yaml(
			global_context.session_files.get_path_to_last_hosting_analysis_time(),
			True
		)

	@staticmethod
	def _set_last_hosting_analysis_time(global_context, last_hosting_analysis_time):
		"""
		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:type last_hosting_analysis_time: datetime.datetime
		"""
		write_yaml(
			global_context.session_files.get_path_to_last_hosting_analysis_time(),
			last_hosting_analysis_time
		)


class TransferStrategy():
	def __init__(self, webspace_name):
		"""
		:type webspace_name: basestring
		"""
		self.webspace_name = webspace_name
		self.need_compression = True
		# Compression level for rsync, None(default) = 6 level of 9 in rsync
		self.compression_level = None
		# Message that shortly describes the strategy
		self.message = messages.DEFAULT_TRANSFER_STRATEGY


class TransferStrategyCalculator():
	def __init__(self):
		logger.debug(messages.TRANSFER_STRATEGY_CALCULATOR_WAS_CREATED)
		self.webspaces = {}
		self.environment = None

	def _clear_environment_input_data(self):
		logger.debug(messages.INFO_ABOUT_ENVIRONMENT_WAS_CLEARED)
		self.environment = None

	def load_data(self, input_xml_file):
		"""
		:type input_xml_file: basestring
		"""
		logger.debug(messages.TRYING_TO_LOAD_INPUT_XML_FILE % input_xml_file)
		if os.path.exists(input_xml_file) and os.path.isfile(input_xml_file):
			xml = ElementTree.parse(input_xml_file)
		else:
			raise Exception(messages.UNABLE_TO_LOAD_INPUT_XML_DATA % input_xml_file)

		root = xml.getroot()
		if self._is_xml_structure_correct(root):
			logger.debug(messages.DEBUG_PARSING_XML_FILE, input_xml_file)
			for section in root:
				if section.tag == 'files':
					logger.debug(messages.PARSING_FILES_SECTION_OF_FILE % input_xml_file)
					for webspace in section:
						webspace_name = normalize_domain_name(webspace.find('name').text)
						self.webspaces[webspace_name] = Webspace(
							name=webspace_name,
							directory=webspace.find('dir').text,
							bin_c=webspace.find('bin-files-counter').text,
							txt_c=webspace.find('txt-files-counter').text,
							unk_c=webspace.find('unk-files-counter').text,
							all_c=webspace.find('all-files-counter').text,
							bin_s=webspace.find('bin-files-size').text,
							txt_s=webspace.find('txt-files-size').text,
							unk_s=webspace.find('unk-files-size').text,
							all_s=webspace.find('all-files-size').text,
							symlinks_c=webspace.find('symlinks').text,
							unproc_c=webspace.find('unprocessed').text
						)
					logger.debug(messages.LOG_COMPLETED)
				elif (section.tag == 'os'):
					self._clear_environment_input_data()
					logger.debug(messages.PARSING_ENVIRONMENT_SECTION_OF_FILE % input_xml_file)
					self.environment = Environment(
						cpu_usage=section.find('cpu-usage').text,
						ram_usage=section.find('ram-usage').text,
						hdd_write=section.find('hdd-write').text,
						hdd_read=section.find('hdd-read').text,
						network=section.find('network').text
					)
					logger.debug(messages.LOG_COMPLETED)
			logger.debug(messages.XML_FILE_WAS_SUCCESSFULLY_PARSED % input_xml_file)
		else:
			logger.warning(messages.INPUT_XML_FILE_IS_CORRUPTED % input_xml_file)

	def _is_xml_structure_correct(self, root):
		"""
		:type root: xml.etree.ElementTree.Element
		:rtype: bool
		"""
		logger.debug(messages.CHECKING_INPUT_XML_STRUCTURE)
		for section in root:
			if section.tag == 'files':
				webspaces = section
				for webspace in webspaces:
					if webspace.find('name') is None or \
						webspace.find('dir') is None or \
						webspace.find('bin-files-counter') is None or \
						webspace.find('txt-files-counter') is None or \
						webspace.find('unk-files-counter') is None or \
						webspace.find('all-files-counter') is None or \
						webspace.find('bin-files-size') is None or \
						webspace.find('txt-files-size') is None or \
						webspace.find('unk-files-size') is None or \
						webspace.find('all-files-size') is None or \
						webspace.find('symlinks') is None or \
						webspace.find('unprocessed') is None:
							logger.debug(messages.INPUT_XML_IS_CORRUPTED_IN_FILES_SECTION)
							return False
			elif section.tag == 'os':
				environment = section
				if environment.find('cpu-usage') is None or \
					environment.find('cpu-usage') is None or \
					environment.find('ram-usage') is None or \
					environment.find('hdd-write') is None or \
					environment.find('hdd-read') is None or \
					environment.find('network') is None:
						logger.debug(messages.INPUT_XML_IS_CORRUPTED_IN_OS_SECTION)
						return False
		logger.debug(messages.DEBUG_INPUT_XML_OK)
		return True

	def get_known_strategy(self, webspace_name):
		"""
		:type webspace_name: basestring
		:rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
		"""
		strategy = TransferStrategy(webspace_name)
		if webspace_name not in self.webspaces.keys():
			logger.info(messages.UNABLE_TO_FIND_INFO_ABOUT_WEBSPACE % webspace_name)
			return strategy
		# (CPU, RAM, HDD, NET). example: (CRITICAL, ANY, ANY, ANY)
		env_estimation = self.environment.get_estimation()
		most_effective_strategy = self._select_strategy(env_estimation)
		logger.debug(messages.FOLLOWING_CONTENT_TRANSFER_STRATEGY_WAS_SELECTED % str(most_effective_strategy))
		if most_effective_strategy['compression'] is True:
			strategy.need_compression = True
		elif most_effective_strategy['compression'] is False:
			strategy.need_compression = False
		else:
			logger.debug(messages.UNABLE_TO_DETERMINE_PROPER_STRATEGY)
		strategy.message = most_effective_strategy['msg']
		return strategy

	def get_math_analyse(self, webspace_name):
		"""
		:type webspace_name: basestring
		:rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
		"""
		strategy = TransferStrategy(webspace_name)
		# check webspace & environment analysis values before calculation
		if normalize_domain_name(webspace_name) not in self.webspaces.keys():
			logger.warning(messages.UNABLE_TO_FIND_INFO_ABOUT_WEBSPACE % webspace_name)
			return strategy
		if not self.environment.is_cpu_analysed():
			logger.warning(messages.UNABLE_TO_DETECT_CPU_USAGE)
			return strategy
		if not self.environment.is_network_analysed() or self.environment.network <= 0:
			logger.warning(messages.UNABLE_TO_DETECT_NETWORK_SPEED)
			return strategy

		try:
			(rsync_acceleration, compression_level) = self.estimate_rsync_acceleration(
				self.webspaces[normalize_domain_name(webspace_name)],
				self.environment
			)
			if rsync_acceleration is None:
				logger.info(messages.NOT_ALL_ENVIRONMENT_PARAMETERS_WERE_DETECTED)
				return strategy
			strategy.message = messages.FASTER_CALCULATION % (compression_level, str(round(rsync_acceleration, 2)))
			if rsync_acceleration > 1:
				logger.debug(messages.DATA_COMPRESSION_IS_RECOMMENDED)
				strategy.need_compression = True
				strategy.compression_level = compression_level
			else:
				logger.debug(messages.DATA_COMPRESSION_IS_NOT_RECOMMENDED)
				strategy.need_compression = False
		except Exception as e:
			logger.warning(messages.UNABLE_TO_ESTIMATE_RSYNC_ACCELERATION)
			logger.debug(u"Exception: ", exc_info=e)
		return strategy

	def estimate_rsync_acceleration(self, webspace, environment):
		"""
		:type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
		:type environment: parallels.plesk.hosting_analyser.hosting_analyser.Environment
		:rtype: tuple(float , int) | tuple(float, None) | tuple(None, None)
		"""
		rsync_acceleration = None
		compression_level = None
		context = Registry.get_instance().get_context()
		coefficients = get_coefficients_for_windows() if context.conn.target.is_windows else get_coefficients_for_unix()
		# (k/bin/, k/txt/, k/unk/, k/hdd/, k1/compr/, k2/compr/, compression_level)
		for (k_bin, k_txt, k_unk, k_hdd, k_compr_1, k_compr_2, _compression_level) in coefficients:
			logger.debug(messages.CALCULATING_RSYNC_ACCELERATION_FOR_COMPRESSION_LEVEL % _compression_level)
			var1 = k_bin*webspace.bin_size + k_txt*webspace.txt_size + k_unk*webspace.unk_size
			var2 = (
				environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd) +
				environment.get_webspace_file_compress_rough_time(webspace, k_compr_1, k_compr_2)
			) * environment.network
			var3 = webspace.all_size + environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd) * environment.network
			if var1 + var2 <= 0:
				continue
			_rsync_acceleration = var3 / (var1 + var2)
			try:
				# debug more information, not necessary information which could return ZeroDivision exceptions sometimes
				debug_var_hdd_read = environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd)
				debug_var_file_compr = environment.get_webspace_file_compress_rough_time(webspace, k_compr_1, k_compr_2)
				debug_var_send_w_compr = environment.get_webspace_transfer_time_with_compression(webspace, k_bin, k_txt, k_unk)
				debug_var_send_wo_compr = environment.get_webspace_transfer_time_without_compression(webspace)
				logger.debug(
					messages.DEBUG_NET_SPEED,
					str(get_bytes_with_suffix(environment.network))
				)
				logger.debug(
					messages.ESTIMATED_TIME_FOR_READING_FILES_FROM_HDD,
					get_time_duration_for_human(debug_var_hdd_read)
				)
				logger.debug(
					messages.ESTIMATED_TIME_FOR_COMPRESSING_FILES,
					get_time_duration_for_human(debug_var_file_compr)
				)
				logger.debug(
					messages.ESTIMATED_TIME_FOR_SENDING_FILES_WITH_COMPRESSION,
					get_time_duration_for_human(debug_var_send_w_compr)
				)
				logger.debug(
					messages.ESTIMATED_TIME_FOR_SENDING_FILES_NO_COMPRESSION,
					get_time_duration_for_human(debug_var_send_wo_compr)
				)
				logger.debug(messages.DEBUG_RSYNC_ACC_COEF, _rsync_acceleration)
				logger.debug(
					messages.DEBUG_EST_TIME_COMPRESSION_NO_CACHE,
					get_time_duration_for_human(debug_var_hdd_read + debug_var_send_w_compr + debug_var_file_compr)
				)
				logger.debug(
					messages.DEBUG_EST_TIME_NO_COMPRESSION_NO_CACHE,
					get_time_duration_for_human(debug_var_send_wo_compr + debug_var_hdd_read)
				)
				logger.debug(
					messages.DEBUG_EST_TIME_COMPRESSION_CACHE %
					get_time_duration_for_human(debug_var_send_w_compr + debug_var_file_compr)
				)
				logger.debug(
					messages.DEBUG_EST_TIME_NO_COMPRESSION_CACHE %
					get_time_duration_for_human(debug_var_send_wo_compr)
				)
			except:
				logger.debug(messages.UNABLE_TO_LOG_DEBUG_INFO_ABOUT_RSYNC_CALCULATION)
			if rsync_acceleration is None or rsync_acceleration < _rsync_acceleration:
				rsync_acceleration = _rsync_acceleration
				compression_level = _compression_level

		return rsync_acceleration, compression_level

	def _select_strategy(self, env_estimation):
		"""
		:type env_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
		:rtype: dict
		"""
		context = Registry.get_instance().get_context()
		strategies = get_windows_strategies() if context.conn.target.is_windows else get_unix_strategies()
		logger.debug(messages.DEBUG_SELECTING_STRATEGY)
		for estimation_pattern, known_strategy in strategies.items():
			if self._is_suitable_estimation(env_estimation, estimation_pattern):
				logger.debug(messages.DEBUG_ENV_EST_FITS, str(env_estimation), str(estimation_pattern))
				return known_strategy
			else:
				logger.debug(messages.DEBUG_ENV_EST_DOES_NOT_FIT, str(env_estimation), str(estimation_pattern))

	# example:
	# env_estimation (source) = (CRITICAL, ANY, ANY, ANY)
	# pattern_estimation (destination) = (ANY, ANY, ANY, ANY)
	@staticmethod
	def _is_suitable_estimation(env_estimation, pattern_estimation):
		"""
		:type env_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
		:type pattern_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
		:rtype: bool
		"""
		for index in range(0, len(env_estimation)):
			if not env_estimation[index].issubset(pattern_estimation[index]):
				return False
		return True


class Webspace():
	def __init__(self, name, directory, bin_c, txt_c, unk_c, all_c, bin_s, txt_s, unk_s, all_s, symlinks_c, unproc_c):
		"""
		:type name: basestring
		:type directory: basestring
		:type bin_c: int
		:type txt_c: int
		:type unk_c: int
		:type all_c: int
		:type bin_s: int
		:type txt_s: int
		:type unk_s: int
		:type all_s: int
		:type symlinks_c: int
		:type unproc_c: int
		"""
		self.name = str(name)
		self.directory = str(directory)
		self.bin_counter = float(bin_c)
		self.txt_counter = float(txt_c)
		self.unk_counter = float(unk_c)
		self.all_counter = float(all_c)
		self.bin_size = float(bin_s)
		self.txt_size = float(txt_s)
		self.unk_size = float(unk_s)
		self.all_size = float(all_s)
		self.symlinks_counter = int(symlinks_c)
		self.unprocessed_counter = int(unproc_c)

	def is_bin_by_size(self):
		"""
		:rtype: bool
		"""
		return True if self.get_bin_files_percent_by_size() >= self.get_txt_files_percent_by_size() else False

	def is_txt_by_size(self):
		"""
		:rtype: bool
		"""
		return True if self.get_txt_files_percent_by_size() >= self.get_bin_files_percent_by_size() else False

	# 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
	def get_bin_files_percent_by_size(self):
		"""
		:rtype: int
		"""
		return self.bin_size * 100 / self.all_size if self.all_size != 0 else 100

	# 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
	def get_txt_files_percent_by_size(self):
		"""
		:rtype: int
		"""
		return self.txt_size * 100 / self.all_size if self.all_size != 0 else 100

	# 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
	def get_unk_files_percent_by_size(self):
		"""
		:rtype: int
		"""
		return self.unk_size * 100 / self.all_size if self.all_size != 0 else 100


class Environment():
	def __init__(self, cpu_usage, ram_usage, hdd_write, hdd_read, network):
		"""
		:type cpu_usage: float
		:type ram_usage: float
		:type hdd_write: float
		:type hdd_read: float
		:type network: float
		"""
		self.unknown = -1  # Unknown result special value
		self.cpu_usage = float(cpu_usage)  # Percent of CPU usage (%)
		self.ram_usage = float(ram_usage)  # Percent of RAM usage (%)
		self.hdd_write = float(hdd_write)  # HDD write speed (bytes/sec)
		self.hdd_read = float(hdd_read)  # HDD read speed (bytes/sec)
		self.network = float(network)  # Network upload speed (bytes/sec)
		logger.debug(messages.DEBUG_ENV_ANALYSIS)
		logger.debug(messages.DEBUG_CPU_USAGE, self.cpu_usage)
		logger.debug(messages.DEBUG_RAM_USAGE, self.ram_usage)
		logger.debug(messages.DEBUG_HDD_WRITE, self.hdd_write)
		logger.debug(messages.DEBUG_HDD_READ, self.hdd_read)
		logger.debug(messages.DEBUG_NET_UPLOAD_SPEED, self.network)

	def is_cpu_analysed(self):
		"""
		:rtype: bool
		"""
		return True if self.cpu_usage != self.unknown else False

	def is_ram_analysed(self):
		"""
		:rtype: bool
		"""
		return True if self.ram_usage != self.unknown else False

	def is_hdd_analysed(self):
		"""
		:rtype: bool
		"""
		return True if self.hdd_write != self.unknown and self.hdd_read != self.unknown else False

	def is_network_analysed(self):
		"""
		:rtype: bool
		"""
		return True if self.network != self.unknown else False

	# TODO implement more complicated logic here
	def get_estimation(self):
		"""
		:rtype: tuple(frozenset, frozenset, frozenset, frozenset)
		"""
		# Order: (CPU, RAM, HDD, NET)
		cpu = UNKNOWN
		if self.is_cpu_analysed():
			if self.cpu_usage > 80:
				cpu = CRITICAL
			else:
				cpu = OK

		ram = UNKNOWN
		if self.is_ram_analysed():
			if self.ram_usage > 90:
				ram = CRITICAL
			else:
				ram = OK

		hdd = UNKNOWN
		if self.is_hdd_analysed():
			# < 2 MB/s
			if self.hdd_write < 2097152 or self.hdd_read < 2097152:
				hdd = CRITICAL
			else:
				hdd = OK

		net = UNKNOWN
		if self.is_network_analysed():
			# < 100 KBps ( set 200-300 KBps here? )
			if self.network < 102400:
				net = CRITICAL
			else:
				net = OK

		env_estimation = (cpu, ram, hdd, net)
		logger.debug(messages.DEBUG_ENV_ESTIMATION, str(env_estimation))
		return env_estimation

	def get_webspace_transfer_time_with_compression(self, webspace, k_bin, k_txt, k_unk):
		"""
		:type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
		:type k_bin: float
		:type k_txt: float
		:type k_unk: float
		:rtype: float
		"""
		if not self.is_network_analysed() or self.network <= 0:
			return self.unknown
		return (k_bin*webspace.bin_size + k_txt*webspace.txt_size + k_unk*webspace.unk_size) / self.network

	def get_webspace_transfer_time_without_compression(self, webspace):
		"""
		:type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
		:rtype: float | int
		"""
		if not self.is_network_analysed() or self.network <= 0:
			return self.unknown
		return webspace.all_size / self.network

	# all_files_counter is much more influencing for the time, than all_file_size
	# so we just ignore all_file_size expecting that we are handling average webspaces:
	# ~4000 files, 150-200Mb, 50%/50% - bin/txt
	# Also, we do not take into account HDD utilisation yet,
	# it's not mandatory because of fixed maximal HDD speed (~150 Mbps for SATA)
	# Coefficient was got for SATA HDD in mostly idle mode
	# we calculate ratio between transfer time with compression and without compression,
	# so innacuracity of time of HDD reading will be reduced in final result (numerator & denominator of the formula has it)
	@staticmethod
	def get_webspace_hdd_reading_rought_time(webspace, k_hdd):
		"""
		:type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
		:type k_hdd: float
		:rtype: float
		"""
		return k_hdd * webspace.all_counter

	# Depends on: all_files_size [bytes], cpu_usage [percent]
	# This relation formula was got for the following hardware configuration:
	# - OS: Windows 7 SP1 (x64)
	# - CPU AMD 6 Cores 3.50GHz each
	# - RAM 16 Gb
	# - HDD Seagate ST500DM002-1BD14 SCSI
	# CPU utilisation tests were produced for cases when 1-6 CPU cores were busy
	def get_webspace_file_compress_rough_time(self, webspace, k_compr_1, k_compr_2, compression = 6):
		"""
		:type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
		:type k_compr_1: float
		:type k_compr_2: float
		:type compression: int
		:rtype: float | int
		"""
		# fixme: this formula was got for default zlib compression level - 6 and could affect the result for another compression level
		if not self.is_cpu_analysed():
			return self.unknown
		cpu_usage = self.cpu_usage
		if cpu_usage >= 100:
			cpu_usage = 99
		if compression == 6:
			size_in_megabytes = webspace.all_size / 1048576
			return size_in_megabytes*(k_compr_1+(k_compr_2/(100-cpu_usage)))
		return self.unknown
