plugin_cpu: latency can be now specified as cstate

Maximal latency can be now specified multiple ways:
- directly in usec (this is the same as before), e.g. for 10 us:
  force_latency = 10
- as an ID of maximal cstate allowed, e.g. for the kernel state1:
  force_latency = cstate.id:1
- as a name (case sensitive) of maximal cstate allowed, e.g. for the state named C1:
  force_latency = cstate.name:C1

It is also possible to specify multiple fallback values separated by '|', e.g.:
  force_latency = cstate.name:C6|cstate.id:4|10

This will try to obtain latency of cstate named C6, if it fails (e.g.
there is no such cstate), it will try kernel state4 and if it also fails
it finally fallbacks to 10 us.

This commit also changes force_latency settings of latency-performance
profile to:
  force_latency=cstate.id:1|1

I.e. it tries kernel state1 and fallbacks to 1 us.

Resolves: rhbz#1737628

diff --git a/usr/lib/tuned/latency-performance/tuned.conf b/usr/lib/tuned/latency-performance/tuned.conf
index 054c554..4cd69a1 100644
--- a/usr/lib/tuned/latency-performance/tuned.conf
+++ b/usr/lib/tuned/latency-performance/tuned.conf
@@ -6,7 +6,7 @@
 summary=Optimize for deterministic performance at the cost of increased power consumption
 
 [cpu]
-force_latency=1
+force_latency=cstate.id:1|1
 governor=performance
 energy_perf_bias=performance
 min_perf_pct=100
diff --git a/usr/lib/tuned/virtual-host/tuned.conf b/usr/lib/tuned/virtual-host/tuned.conf
index ca493d6..8acb92d 100644
--- a/usr/lib/tuned/virtual-host/tuned.conf
+++ b/usr/lib/tuned/virtual-host/tuned.conf
@@ -18,4 +18,4 @@ kernel.sched_migration_cost_ns = 5000000
 
 [cpu]
 # Setting C3 state sleep mode/power savings
-force_latency=70
+force_latency=cstate.id:3|70
diff --git a/usr/lib/python2.7/site-packages/tuned/plugins/plugin_cpu.py b/usr/lib/python2.7/site-packages/tuned/plugins/plugin_cpu.py
index 77e9448..04c7b19 100644
--- a/usr/lib/python2.7/site-packages/tuned/plugins/plugin_cpu.py
+++ b/usr/lib/python2.7/site-packages/tuned/plugins/plugin_cpu.py
@@ -12,8 +12,7 @@ import procfs
 
 log = tuned.logs.get()
 
-# TODO: force_latency -> command
-#       intel_pstate
+cpuidle_states_path = "/sys/devices/system/cpu/cpu0/cpuidle"
 
 class CPULatencyPlugin(base.Plugin):
 	"""
@@ -210,13 +209,74 @@ class CPULatencyPlugin(base.Plugin):
 	def _instance_unapply_dynamic(self, instance, device):
 		pass
 
+	def _str2int(self, s):
+		try:
+			return int(s)
+		except (ValueError, TypeError):
+			return None
+
+	def _read_cstates_latency(self):
+		self.cstates_latency = {}
+		for d in os.listdir(cpuidle_states_path):
+			cstate_path = cpuidle_states_path + "/%s/" % d
+			name = self._cmd.read_file(cstate_path + "name", err_ret = None, no_error = True)
+			latency = self._cmd.read_file(cstate_path + "latency", err_ret = None, no_error = True)
+			if name is not None and latency is not None:
+				latency = self._str2int(latency)
+				if latency is not None:
+					self.cstates_latency[name.strip()] = latency
+
+	def _get_latency_by_cstate_name(self, name):
+		log.debug("getting latency for cstate with name '%s'" % name)
+		if self.cstates_latency is None:
+			log.debug("reading cstates latency table")
+			self._read_cstates_latency()
+		latency = self.cstates_latency.get(name, None)
+		log.debug("cstate name mapped to latency: %s" % str(latency))
+		return latency
+
+	def _get_latency_by_cstate_id(self, lid):
+		log.debug("getting latency for cstate with ID '%s'" % str(lid))
+		lid = self._str2int(lid)
+		if lid is None:
+			log.debug("cstate ID is invalid")
+			return None
+		latency_path = cpuidle_states_path + "/%s/latency" % ("state%d" % lid)
+		latency = self._str2int(self._cmd.read_file(latency_path, err_ret = None, no_error = True))
+		log.debug("cstate ID mapped to latency: %s" % str(latency))
+		return latency
+
+	def _parse_latency(self, latency):
+		self.cstates_latency = None
+		latencies = str(latency).split("|")
+		log.debug("parsing latency")
+		for latency in latencies:
+			try:
+				latency = int(latency)
+				log.debug("parsed directly specified latency value: %d" % latency)
+			except ValueError:
+				if latency[0:10] == "cstate.id:":
+					latency = self._get_latency_by_cstate_id(latency[10:])
+				elif latency[0:12] == "cstate.name:":
+					latency = self._get_latency_by_cstate_name(latency[12:])
+				else:
+					latency = None
+					log.debug("invalid latency specified: '%s'" % str(latency))
+			if latency is not None:
+				break
+		return latency
+
 	def _set_latency(self, latency):
-		latency = int(latency)
-		if self._has_pm_qos and self._latency != latency:
-			log.info("setting new cpu latency %d" % latency)
-			latency_bin = struct.pack("i", latency)
-			os.write(self._cpu_latency_fd, latency_bin)
-			self._latency = latency
+		latency = self._parse_latency(latency)
+		if self._has_pm_qos:
+			if latency is None:
+				log.error("unable to evaluate latency value (probably wrong settings in the 'cpu' section of current profile), disabling PM QoS")
+				self._has_pm_qos = False
+			elif self._latency != latency:
+				log.info("setting new cpu latency %d" % latency)
+				latency_bin = struct.pack("i", latency)
+				os.write(self._cpu_latency_fd, latency_bin)
+				self._latency = latency
 
 	def _get_available_governors(self, device):
 		return self._cmd.read_file("/sys/devices/system/cpu/%s/cpufreq/scaling_available_governors" % device).strip().split()
