35d88e8caeb0721d7e884cffdcc4ed4179c40460
1#
2# temperature.py
3#
4# Find current temperature of various system components (CPU, motherboard,
5# hard drives, ambient). Detection of motherboard-based temperatures (CPU
6# etc) uses the pysensors library, and produces a similar output to
7# lmsensors. HDD temperatures are obtained from the hddtemp daemon
8# (http://www.guzu.net/linux/hddtemp.php) which was orphaned since 2007. For
9# hddtemp to work, it must be started in daemon mode, either manually or with
10# a unit file. Manually, it would be started like this:
11#
12# sudo hddtemp -d /dev/sda /dev/sdb ... /dev/sdX
13#
14
15import re
16import sensors
17import socket, sys
18from telnetlib import Telnet
19from typing import List, Dict, NamedTuple
20
21from ..formatting import *
22from ..util import readlog, resolve
23from .. import config
24
25import logging
26logger = logging.getLogger(__name__)
27
28class Drive(NamedTuple):
29 path: str
30 model: str
31 temperature: int
32 units: str
33
34class HddtempClient:
35
36 def __init__(self, host: str='localhost', port: int=7634, timeout: int=10, sep: str='|') -> None:
37 self.host = host
38 self.port = port
39 self.timeout = timeout
40 self.sep = sep
41
42 def _parse_drive(self, drive: str) -> Drive:
43 drive_data = drive.split(self.sep)
44 return Drive(drive_data[0], drive_data[1], int(drive_data[2]), drive_data[3])
45
46 def _parse(self, data: str) -> List[Drive]:
47 line = data.lstrip(self.sep).rstrip(self.sep) # Remove first/last
48 drives = line.split(self.sep * 2)
49 return [self._parse_drive(drive) for drive in drives]
50
51 def get_drives(self) -> List[Drive]: # Obtain data from telnet server
52 try:
53 with Telnet(self.host, self.port, timeout=self.timeout) as tn:
54 data = tn.read_all()
55 return self._parse(data.decode('ascii')) # Return parsed data
56 except:
57 logger.warning("Couldn't read data from {0}:{1}".format(self.host, self.port))
58 return 1
59
60
61def parse_log():
62 logger.debug("Starting temp section")
63 output = writetitle("temperatures")
64 output += opentag('div', 1, 'temp', 'section')
65
66 # cpu temp
67
68 sensors.init()
69 coretemps = []
70 pkgtemp = 0
71 systemp = 0
72 try:
73 for chip in sensors.iter_detected_chips():
74 for feature in chip:
75 if "Core" in feature.label:
76 coretemps.append([feature.label, feature.get_value()])
77 logger.debug("found core " + feature.label + " at temp " + str(feature.get_value()))
78 if "CPUTIN" in feature.label:
79 pkgtemp = str(feature.get_value())
80 logger.debug("found cpu package at temperature " + pkgtemp)
81 if "SYS" in feature.label:
82 systemp = feature.get_value()
83 logger.debug("found sys input " + feature.label + " at temp " + str(feature.get_value()))
84 logger.debug("Core temp data is: " + str(coretemps))
85# core_avg = reduce(lambda x, y: x[1] + y[1], coretemps) / len(coretemps)
86 core_avg = sum(core[1] for core in coretemps) / len(coretemps)
87 logger.debug("average cpu temp is " + str(core_avg))
88 coretemps.append(["avg", str(core_avg)])
89 coretemps.append(["pkg", pkgtemp])
90 coretemps = [x[0] + ": " + str(x[1]) + DEG + CEL for x in coretemps]
91 finally:
92 sensors.cleanup()
93
94 if (systemp != 0):
95 output += writedata("sys: " + str(systemp) + DEG)
96 if (coretemps != ''):
97 output += writedata("cores", coretemps)
98
99 logger.info("Finished reading onboard temperatures")
100
101 # drive temp
102
103 # For this to work, `hddtemp` must be running in daemon mode.
104 # Start it like this (bash): sudo hddtemp -d /dev/sda /dev/sdX...
105
106 received = ''
107 sumtemp = 0.0
108 data = ""
109 fields = []
110
111 client = HddtempClient(host=config.prefs['hddtemp']['host'], port=int(config.prefs['hddtemp']['port']), sep=config.prefs['hddtemp']['separator'], timeout=int(config.prefs['hddtemp']['timeout']))
112 drives = client.get_drives()
113 for drive in sorted(drives, key=lambda x: x.path):
114 if drive.path in config.prefs['hddtemp']['drives']:
115 sumtemp += drive.temperature
116 fields.append(("{0} ({1})".format(drive.path, drive.model) if config.prefs['hddtemp']['show-model'] else drive.path) + ": {0}{1}{2}".format(drive.temperature, DEG, drive.units))
117 else:
118 drives.remove(drive)
119 logger.debug("Ignoring drive {0} ({1})due to config".format(drive.path, drive.model))
120 logger.debug("Received drive info: " + str(drives))
121
122 hddavg = '{0:.1f}{1}{2}'.format(sumtemp/len(drives), DEG, drives[0].units) # use units of first drive
123 logger.debug("Sum of temperatures: {}; Number of drives: {}; => Avg disk temp is {}".format(str(sumtemp), str(len(drives)), hddavg))
124 fields.append("avg: " + str(hddavg))
125
126 if (prefs['hddtemp']['drives'] != ''):
127 output += writedata("disks", fields)
128 logger.info("Finished processing drive temperatures")
129
130
131 output += closetag('div', 1)
132 logger.info("Finished temp section")
133 return output