Some modifications to the Temperature module:
* Support multiple CPUs/cores * Support dynamically discovering warning/critical limits * Support dynamic colors * Add vertical bar formatters Should't break existing configs.
This commit is contained in:
parent
4d2ccc5dcb
commit
10e00263b3
@ -48,6 +48,7 @@ MOCK_MODULES = [
|
|||||||
"dateutil.parser",
|
"dateutil.parser",
|
||||||
"dateutil.relativedelta",
|
"dateutil.relativedelta",
|
||||||
"xkbgroup",
|
"xkbgroup",
|
||||||
|
"sensors"
|
||||||
]
|
]
|
||||||
|
|
||||||
for mod_name in MOCK_MODULES:
|
for mod_name in MOCK_MODULES:
|
||||||
|
@ -1,17 +1,156 @@
|
|||||||
from i3pystatus import IntervalModule
|
from i3pystatus import IntervalModule
|
||||||
|
from i3pystatus.core.color import ColorRangeModule
|
||||||
|
from i3pystatus.core.util import make_vertical_bar
|
||||||
|
|
||||||
|
|
||||||
class Temperature(IntervalModule):
|
class Sensor:
|
||||||
|
"""
|
||||||
|
Simple class representing a CPU temperature sensor.
|
||||||
"""
|
"""
|
||||||
Shows CPU temperature of Intel processors
|
|
||||||
|
|
||||||
AMD is currently not supported as they can only report a relative temperature, which is pretty useless
|
def __init__(self, name, current, maximum, critical):
|
||||||
|
self.name = name.replace(' ', '_')
|
||||||
|
self.current = int(current)
|
||||||
|
self.maximum = int(maximum)
|
||||||
|
self.critical = int(critical)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Sensor(name='{}', current={}, maximum={}, critical={})".format(
|
||||||
|
self.name,
|
||||||
|
self.current,
|
||||||
|
self.maximum,
|
||||||
|
self.critical
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_warning(self):
|
||||||
|
return self.current > self.maximum
|
||||||
|
|
||||||
|
def is_critical(self):
|
||||||
|
return self.current > self.critical
|
||||||
|
|
||||||
|
|
||||||
|
def get_sensors():
|
||||||
|
""" Detect and return a list of Sensor objects """
|
||||||
|
import sensors
|
||||||
|
found_sensors = list()
|
||||||
|
|
||||||
|
def get_subfeature_value(feature, subfeature_type):
|
||||||
|
subfeature = chip.get_subfeature(feature, subfeature_type)
|
||||||
|
if subfeature:
|
||||||
|
return chip.get_value(subfeature.number)
|
||||||
|
|
||||||
|
for chip in sensors.get_detected_chips():
|
||||||
|
for feature in chip.get_features():
|
||||||
|
if feature.type == sensors.FEATURE_TEMP:
|
||||||
|
name = chip.get_label(feature)
|
||||||
|
max = get_subfeature_value(feature, sensors.SUBFEATURE_TEMP_MAX)
|
||||||
|
current = get_subfeature_value(feature, sensors.SUBFEATURE_TEMP_INPUT)
|
||||||
|
critical = get_subfeature_value(feature, sensors.SUBFEATURE_TEMP_CRIT)
|
||||||
|
if critical:
|
||||||
|
found_sensors.append(Sensor(name=name, current=current, maximum=max, critical=critical))
|
||||||
|
return found_sensors
|
||||||
|
|
||||||
|
|
||||||
|
class Temperature(IntervalModule, ColorRangeModule):
|
||||||
|
"""
|
||||||
|
Shows CPU temperature of Intel processors.
|
||||||
|
|
||||||
|
AMD is currently not supported as they can only report a relative temperature, which is pretty useless.
|
||||||
|
|
||||||
|
Requires `colour` module from PyPi
|
||||||
|
|
||||||
|
.. rubric:: Modes of operation
|
||||||
|
|
||||||
|
If lm_sensors_enabled is set to False, the module operates in default mode. This means that:
|
||||||
|
* only the {temp} formatter is available
|
||||||
|
* alert_temp is honored
|
||||||
|
|
||||||
|
If lm_sensors_enabled is set to True, the module operates in lm_sensors mode. This means that:
|
||||||
|
* pysensors must be installed (https://github.com/bastienleonard/pysensors)
|
||||||
|
* CPU sensors are discovered dynamically (supporting a sensor per core and multiple CPUs)
|
||||||
|
* alert_temp is ignored. The warning or critical values reported by the sensor are used instead (see urgent_on)
|
||||||
|
|
||||||
|
.. rubric:: lm_sensors installation
|
||||||
|
|
||||||
|
In order to take advantage of the lm_sensors library and tools, it must first be installed and configured.
|
||||||
|
|
||||||
|
On Arch this is as simple as:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pacman -S lm_sensors
|
||||||
|
|
||||||
|
|
||||||
|
On Ubuntu:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo apt-get update && sudo apt-get install lm-sensors libsensors4-dev
|
||||||
|
|
||||||
|
|
||||||
|
The Arch Wiki has a good page on the library - https://wiki.archlinux.org/index.php/lm_sensors
|
||||||
|
|
||||||
|
.. rubric:: lm_sensors_mode formatters
|
||||||
|
|
||||||
|
When ``lm_sensors_enabled`` is True the formatters are created dynamically. In order to discover the formatters that
|
||||||
|
are available, it is best to run the sensors command:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
⇒ sensors
|
||||||
|
coretemp-isa-0000
|
||||||
|
Adapter: ISA adapter
|
||||||
|
Physical id 0: +48.0°C (high = +80.0°C, crit = +99.0°C)
|
||||||
|
Core 0: +48.0°C (high = +80.0°C, crit = +99.0°C)
|
||||||
|
Core 1: +46.0°C (high = +80.0°C, crit = +99.0°C)
|
||||||
|
Core 2: +43.0°C (high = +80.0°C, crit = +99.0°C)
|
||||||
|
Core 3: +47.0°C (high = +80.0°C, crit = +99.0°C)
|
||||||
|
|
||||||
|
The module replaces spaces in sensor names with underscores, therefore from the above output we can
|
||||||
|
identify the following sensor formatters:
|
||||||
|
|
||||||
|
* Physical_id_0
|
||||||
|
* Core_0
|
||||||
|
* Core_1
|
||||||
|
* Core_2
|
||||||
|
* Core_3
|
||||||
|
|
||||||
|
For each sensor a vertical bar is also generated. In this example we would also have the following bars:
|
||||||
|
|
||||||
|
* Physical_id_0_bar
|
||||||
|
* Core_0_bar
|
||||||
|
* Core_1_bar
|
||||||
|
* Core_2_bar
|
||||||
|
* Core_3_bar
|
||||||
|
|
||||||
|
Thus, this format string would be valid: "{Physical_id_0}°C {Core_0_bar}{Core_1_bar}{Core_2_bar}{Core_3_bar}"
|
||||||
|
|
||||||
|
.. rubric:: Pango Markup and lm_sensors_mode
|
||||||
|
|
||||||
|
When Pango Markup is enabled and ``dynamic_color`` is True, each sensor's formatter color is displayed independently.
|
||||||
|
The color is determined by the proximity of the sensors current value to it's critical value.
|
||||||
|
|
||||||
|
.. rubric:: Example Configuration
|
||||||
|
|
||||||
|
Here is an example configuration based on the sensor values discovered above:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
status.register("temp",
|
||||||
|
format="{Physical_id_0}°C {Core_0_bar}{Core_1_bar}{Core_2_bar}{Core_3_bar}",
|
||||||
|
hints={"markup": "pango"},
|
||||||
|
lm_sensors_enabled=True,
|
||||||
|
dynamic_color=True)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
settings = (
|
settings = (
|
||||||
("format",
|
("format",
|
||||||
"format string used for output. {temp} is the temperature in degrees celsius"),
|
"format string used for output. {temp} is the temperature in degrees celsius"),
|
||||||
('display_if', 'snippet that gets evaluated. if true, displays the module output'),
|
('display_if', 'snippet that gets evaluated. if true, displays the module output'),
|
||||||
|
('lm_sensors_enabled', 'whether or not lm_sensors should be used for obtaining CPU temperature information'),
|
||||||
|
('urgent_on', 'whether to flag as urgent when temperature exceeds urgent value or critical value '
|
||||||
|
'(requires lm_sensors_enabled)'),
|
||||||
|
('dynamic_color', 'whether to set the color dynamically (overrides alert_color)'),
|
||||||
"color",
|
"color",
|
||||||
"file",
|
"file",
|
||||||
"alert_temp",
|
"alert_temp",
|
||||||
@ -24,12 +163,86 @@ class Temperature(IntervalModule):
|
|||||||
alert_color = "#FF0000"
|
alert_color = "#FF0000"
|
||||||
display_if = 'True'
|
display_if = 'True'
|
||||||
|
|
||||||
|
lm_sensors_enabled = False
|
||||||
|
dynamic_color = False
|
||||||
|
urgent_on = 'warning'
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.pango_enabled = self.hints.get("markup", False) and self.hints["markup"] == "pango"
|
||||||
|
self.colors = self.get_hex_color_range(self.start_color, self.end_color, 100)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
if eval(self.display_if):
|
||||||
|
if self.lm_sensors_enabled:
|
||||||
|
self.output = self.get_output_sensors()
|
||||||
|
else:
|
||||||
|
self.output = self.get_output_original()
|
||||||
|
|
||||||
|
def get_output_original(self):
|
||||||
|
"""
|
||||||
|
Build the output the original way. Requires no third party libraries.
|
||||||
|
"""
|
||||||
with open(self.file, "r") as f:
|
with open(self.file, "r") as f:
|
||||||
temp = float(f.read().strip()) / 1000
|
temp = float(f.read().strip()) / 1000
|
||||||
|
|
||||||
if eval(self.display_if):
|
if self.dynamic_color:
|
||||||
self.output = {
|
color = self.colors[int(self.percentage(int(temp), self.alert_temp))]
|
||||||
"full_text": self.format.format(temp=temp),
|
else:
|
||||||
"color": self.color if temp < self.alert_temp else self.alert_color,
|
color = self.color if temp < self.alert_temp else self.alert_color
|
||||||
}
|
return {
|
||||||
|
"full_text": self.format.format(temp=temp),
|
||||||
|
"color": color,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_output_sensors(self):
|
||||||
|
"""
|
||||||
|
Build the output using lm_sensors. Requires sensors Python module (see docs).
|
||||||
|
"""
|
||||||
|
data = dict()
|
||||||
|
found_sensors = get_sensors()
|
||||||
|
for sensor in found_sensors:
|
||||||
|
data[sensor.name] = self.format_sensor(sensor)
|
||||||
|
data["{}_bar".format(sensor.name)] = self.format_sensor_bar(sensor)
|
||||||
|
data['temp'] = max((s.current for s in found_sensors))
|
||||||
|
return {
|
||||||
|
'full_text': self.format.format(**data),
|
||||||
|
'urgent': self.get_urgent(found_sensors)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_urgent(self, sensors):
|
||||||
|
""" Determine if any sensors should set the urgent flag. """
|
||||||
|
if self.urgent_on not in ('warning', 'critical'):
|
||||||
|
raise Exception("urgent_on must be one of (warning, critical)")
|
||||||
|
for sensor in sensors:
|
||||||
|
if self.urgent_on == 'warning' and sensor.is_warning():
|
||||||
|
return True
|
||||||
|
elif self.urgent_on == 'critical' and sensor.is_critical():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def format_sensor(self, sensor):
|
||||||
|
""" Format a sensor value. If pango is enabled color is per sensor. """
|
||||||
|
current_val = sensor.current
|
||||||
|
if self.pango_enabled:
|
||||||
|
percentage = self.percentage(sensor.current, sensor.critical)
|
||||||
|
if self.dynamic_color:
|
||||||
|
color = self.colors[int(percentage)]
|
||||||
|
else:
|
||||||
|
color = self.color
|
||||||
|
return self.format_pango(color, current_val)
|
||||||
|
return current_val
|
||||||
|
|
||||||
|
def format_sensor_bar(self, sensor):
|
||||||
|
""" Build and format a sensor bar. If pango is enabled bar color is per sensor."""
|
||||||
|
percentage = self.percentage(sensor.current, sensor.critical)
|
||||||
|
bar = make_vertical_bar(int(percentage))
|
||||||
|
if self.pango_enabled:
|
||||||
|
if self.dynamic_color:
|
||||||
|
color = self.colors[int(percentage)]
|
||||||
|
else:
|
||||||
|
color = self.color
|
||||||
|
return self.format_pango(color, bar)
|
||||||
|
return bar
|
||||||
|
|
||||||
|
def format_pango(self, color, value):
|
||||||
|
return '<span color="{}">{}</span>'.format(color, value)
|
||||||
|
Loading…
Reference in New Issue
Block a user