Skip to content

Commit 6d3f4e5

Browse files
committed
Add LibreHardwareMonitor sensors, fix date format to use locale, fix network speed display
1 parent fe061d7 commit 6d3f4e5

File tree

9 files changed

+679
-25
lines changed

9 files changed

+679
-25
lines changed

external/LibreHardwareMonitor/LICENSE

Lines changed: 373 additions & 0 deletions
Large diffs are not rendered by default.
700 KB
Binary file not shown.

library/sensors/sensors.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ def virtual_percent() -> float:
5454

5555
@staticmethod
5656
@abstractmethod
57-
def virtual_used() -> int:
57+
def virtual_used() -> int: # In bytes
5858
pass
5959

6060
@staticmethod
6161
@abstractmethod
62-
def virtual_free() -> int:
62+
def virtual_free() -> int: # In bytes
6363
pass
6464

6565

@@ -71,17 +71,17 @@ def disk_usage_percent() -> float:
7171

7272
@staticmethod
7373
@abstractmethod
74-
def disk_used() -> int:
74+
def disk_used() -> int: # In bytes
7575
pass
7676

7777
@staticmethod
7878
@abstractmethod
79-
def disk_free() -> int:
79+
def disk_free() -> int: # In bytes
8080
pass
8181

8282

8383
class Net(ABC):
8484
@staticmethod
8585
@abstractmethod
86-
def stats(if_name, interval) -> Tuple[int, int, int, int]: # dl rate (B/s), downloaded (B), up rate (B/s), uploaded (B)
86+
def stats(if_name, interval) -> Tuple[int, int, int, int]: # up rate (B/s), uploaded (B), dl rate (B/s), downloaded (B)
8787
pass
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
import clr # from pythonnet package, not clr package !
2+
import math
3+
import os
4+
from typing import Tuple
5+
from statistics import mean
6+
from win32api import *
7+
8+
import psutil
9+
10+
import library.sensors.sensors as sensors
11+
from library.log import logger
12+
13+
# Import LibreHardwareMonitor dll to Python
14+
lhm_dll = os.getcwd() + '\\external\\LibreHardwareMonitor\\LibreHardwareMonitorLib.dll'
15+
# noinspection PyUnresolvedReferences
16+
clr.AddReference(lhm_dll)
17+
# noinspection PyUnresolvedReferences
18+
from LibreHardwareMonitor import Hardware
19+
20+
File_information = GetFileVersionInfo(lhm_dll, "\\")
21+
22+
ms_file_version = File_information['FileVersionMS']
23+
ls_file_version = File_information['FileVersionLS']
24+
25+
logger.debug("Found LibreHardwareMonitorLib %s" % ".".join([str(HIWORD(ms_file_version)), str(LOWORD(ms_file_version)),
26+
str(HIWORD(ls_file_version)),
27+
str(LOWORD(ls_file_version))]))
28+
29+
handle = Hardware.Computer()
30+
handle.IsCpuEnabled = True
31+
handle.IsGpuEnabled = True
32+
handle.IsMemoryEnabled = True
33+
handle.IsMotherboardEnabled = False
34+
handle.IsControllerEnabled = False
35+
handle.IsNetworkEnabled = True
36+
handle.IsStorageEnabled = True
37+
handle.Open()
38+
for hardware in handle.Hardware:
39+
if hardware.HardwareType == Hardware.HardwareType.Cpu:
40+
logger.info("Found CPU: %s" % hardware.Name)
41+
elif hardware.HardwareType == Hardware.HardwareType.Memory:
42+
logger.info("Found Memory: %s" % hardware.Name)
43+
elif hardware.HardwareType == Hardware.HardwareType.GpuNvidia:
44+
logger.info("Found Nvidia GPU: %s" % hardware.Name)
45+
elif hardware.HardwareType == Hardware.HardwareType.GpuAmd:
46+
logger.info("Found AMD GPU: %s" % hardware.Name)
47+
elif hardware.HardwareType == Hardware.HardwareType.GpuIntel:
48+
logger.info("Found Intel GPU: %s" % hardware.Name)
49+
elif hardware.HardwareType == Hardware.HardwareType.Storage:
50+
logger.info("Found Storage: %s" % hardware.Name)
51+
elif hardware.HardwareType == Hardware.HardwareType.Network:
52+
logger.info("Found Network interface: %s" % hardware.Name)
53+
54+
55+
def get_hw_and_update(hwtype: Hardware.HardwareType) -> Hardware.Hardware:
56+
for hardware in handle.Hardware:
57+
if hardware.HardwareType == hwtype:
58+
hardware.Update()
59+
return hardware
60+
return None
61+
62+
63+
def get_net_interface_and_update(if_name: str) -> Hardware.Hardware:
64+
for hardware in handle.Hardware:
65+
if hardware.HardwareType == Hardware.HardwareType.Network and hardware.Name == if_name:
66+
hardware.Update()
67+
return hardware
68+
69+
logger.error("Network interface '%s' not found. Check names in config.yaml." % if_name)
70+
return None
71+
72+
73+
class Cpu(sensors.Cpu):
74+
@staticmethod
75+
def percentage(interval: float) -> float:
76+
cpu = get_hw_and_update(Hardware.HardwareType.Cpu)
77+
for sensor in cpu.Sensors:
78+
if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("CPU Total"):
79+
return float(sensor.Value)
80+
81+
logger.error("CPU load cannot be read")
82+
return math.nan
83+
84+
@staticmethod
85+
def frequency() -> float:
86+
frequencies = []
87+
cpu = get_hw_and_update(Hardware.HardwareType.Cpu)
88+
for sensor in cpu.Sensors:
89+
if sensor.SensorType == Hardware.SensorType.Clock and str(sensor.Name).startswith("CPU Core #"):
90+
frequencies.append(float(sensor.Value))
91+
return mean(frequencies)
92+
93+
@staticmethod
94+
def load() -> Tuple[float, float, float]: # 1 / 5 / 15min avg:
95+
# Get this data from psutil because it is not available from LibreHardwareMonitor
96+
return psutil.getloadavg()
97+
98+
@staticmethod
99+
def is_temperature_available() -> bool:
100+
cpu = get_hw_and_update(Hardware.HardwareType.Cpu)
101+
for sensor in cpu.Sensors:
102+
if sensor.SensorType == Hardware.SensorType.Temperature:
103+
if str(sensor.Name).startswith("Core Average") or str(sensor.Name).startswith("Core Max") or str(
104+
sensor.Name).startswith("CPU Package"):
105+
return True
106+
107+
logger.error("CPU temperature cannot be read")
108+
return False
109+
110+
@staticmethod
111+
def temperature() -> float:
112+
cpu = get_hw_and_update(Hardware.HardwareType.Cpu)
113+
# By default, the average temperature of all CPU cores will be used
114+
for sensor in cpu.Sensors:
115+
if sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith("Core Average"):
116+
return float(sensor.Value)
117+
# If not available, the max core temperature will be used
118+
for sensor in cpu.Sensors:
119+
if sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith("Core Max"):
120+
return float(sensor.Value)
121+
# Otherwise the CPU Package temperature (usually same as max core temperature) will be used
122+
for sensor in cpu.Sensors:
123+
if sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith("CPU Package"):
124+
return float(sensor.Value)
125+
126+
return math.nan
127+
128+
129+
class Gpu(sensors.Gpu):
130+
@staticmethod
131+
def stats() -> Tuple[float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / temp (°C)
132+
gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuAmd)
133+
if gpu_to_use is None:
134+
gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuNvidia)
135+
if gpu_to_use is None:
136+
gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuIntel)
137+
if gpu_to_use is None:
138+
# GPU not supported
139+
return math.nan, math.nan, math.nan, math.nan
140+
141+
load = math.nan
142+
used_mem = math.nan
143+
total_mem = math.nan
144+
temp = math.nan
145+
146+
for sensor in gpu_to_use.Sensors:
147+
if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("GPU Core"):
148+
load = float(sensor.Value)
149+
elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith("GPU Memory Used"):
150+
used_mem = float(sensor.Value)
151+
elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith("GPU Memory Total"):
152+
total_mem = float(sensor.Value)
153+
elif sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith("GPU Core"):
154+
temp = float(sensor.Value)
155+
156+
return load, (used_mem / total_mem * 100.0), used_mem, temp
157+
158+
@staticmethod
159+
def is_available() -> bool:
160+
found_amd = (get_hw_and_update(Hardware.HardwareType.GpuAmd) is not None)
161+
found_nvidia = (get_hw_and_update(Hardware.HardwareType.GpuNvidia) is not None)
162+
found_intel = (get_hw_and_update(Hardware.HardwareType.GpuIntel) is not None)
163+
164+
if found_amd and (found_nvidia or found_intel) or (found_nvidia and found_intel):
165+
logger.warning(
166+
"Found multiple GPUs on your system. Will use dedicated GPU (AMD/Nvidia) for stats if possible.")
167+
168+
return found_amd or found_nvidia or found_intel
169+
170+
171+
class Memory(sensors.Memory):
172+
@staticmethod
173+
def swap_percent() -> float:
174+
memory = get_hw_and_update(Hardware.HardwareType.Memory)
175+
176+
virtual_mem_used = math.nan
177+
mem_used = math.nan
178+
virtual_mem_available = math.nan
179+
mem_available = math.nan
180+
181+
# Get virtual / physical memory stats
182+
for sensor in memory.Sensors:
183+
if sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Virtual Memory Used"):
184+
virtual_mem_used = int(sensor.Value)
185+
elif sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Memory Used"):
186+
mem_used = int(sensor.Value)
187+
elif sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith(
188+
"Virtual Memory Available"):
189+
virtual_mem_available = int(sensor.Value)
190+
elif sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Memory Available"):
191+
mem_available = int(sensor.Value)
192+
193+
# Compute swap stats from virtual / physical memory stats
194+
swap_used = virtual_mem_used - mem_used
195+
swap_available = virtual_mem_available - mem_available
196+
swap_total = swap_used + swap_available
197+
198+
return swap_used / swap_total * 100.0
199+
200+
@staticmethod
201+
def virtual_percent() -> float:
202+
memory = get_hw_and_update(Hardware.HardwareType.Memory)
203+
for sensor in memory.Sensors:
204+
if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("Memory"):
205+
return float(sensor.Value)
206+
207+
return math.nan
208+
209+
@staticmethod
210+
def virtual_used() -> int: # In bytes
211+
memory = get_hw_and_update(Hardware.HardwareType.Memory)
212+
for sensor in memory.Sensors:
213+
if sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Memory Used"):
214+
return int(sensor.Value * 1000000000.0)
215+
216+
return 0
217+
218+
@staticmethod
219+
def virtual_free() -> int: # In bytes
220+
memory = get_hw_and_update(Hardware.HardwareType.Memory)
221+
for sensor in memory.Sensors:
222+
if sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Memory Available"):
223+
return int(sensor.Value * 1000000000.0)
224+
225+
return 0
226+
227+
228+
class Disk(sensors.Disk):
229+
@staticmethod
230+
def disk_usage_percent() -> float:
231+
disk = get_hw_and_update(Hardware.HardwareType.Storage)
232+
for sensor in disk.Sensors:
233+
if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("Used Space"):
234+
return float(sensor.Value)
235+
236+
return math.nan
237+
238+
@staticmethod
239+
def disk_used() -> int: # In bytes
240+
# Get this data from psutil because it is not available from LibreHardwareMonitor
241+
return psutil.disk_usage("/").used
242+
243+
@staticmethod
244+
def disk_free() -> int: # In bytes
245+
# Get this data from psutil because it is not available from LibreHardwareMonitor
246+
return psutil.disk_usage("/").free
247+
248+
249+
class Net(sensors.Net):
250+
@staticmethod
251+
def stats(if_name, interval) -> Tuple[
252+
int, int, int, int]: # up rate (B/s), uploaded (B), dl rate (B/s), downloaded (B)
253+
254+
dl_speed = 0
255+
dl = 0
256+
up_speed = 0
257+
up = 0
258+
259+
if if_name != "":
260+
net_if = get_net_interface_and_update(if_name)
261+
if net_if is not None:
262+
for sensor in net_if.Sensors:
263+
if sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith("Data Uploaded"):
264+
up = int(sensor.Value * 1000000000.0)
265+
elif sensor.SensorType == Hardware.SensorType.Data and str(sensor.Name).startswith(
266+
"Data Downloaded"):
267+
dl = int(sensor.Value * 1000000000.0)
268+
elif sensor.SensorType == Hardware.SensorType.Throughput and str(sensor.Name).startswith(
269+
"Upload Speed"):
270+
up_speed = int(sensor.Value)
271+
elif sensor.SensorType == Hardware.SensorType.Throughput and str(sensor.Name).startswith(
272+
"Download Speed"):
273+
dl_speed = int(sensor.Value)
274+
275+
return up_speed, up, dl_speed, dl

library/sensors/sensors_python.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,11 @@ def virtual_percent() -> float:
224224
return psutil.virtual_memory().percent
225225

226226
@staticmethod
227-
def virtual_used() -> int:
227+
def virtual_used() -> int: # In bytes
228228
return psutil.virtual_memory().used
229229

230230
@staticmethod
231-
def virtual_free() -> int:
231+
def virtual_free() -> int: # In bytes
232232
return psutil.virtual_memory().free
233233

234234

@@ -238,18 +238,18 @@ def disk_usage_percent() -> float:
238238
return psutil.disk_usage("/").percent
239239

240240
@staticmethod
241-
def disk_used() -> int:
241+
def disk_used() -> int: # In bytes
242242
return psutil.disk_usage("/").used
243243

244244
@staticmethod
245-
def disk_free() -> int:
245+
def disk_free() -> int: # In bytes
246246
return psutil.disk_usage("/").free
247247

248248

249249
class Net(sensors.Net):
250250
@staticmethod
251251
def stats(if_name, interval) -> Tuple[
252-
int, int, int, int]: # dl rate (B/s), downloaded (B), up rate (B/s), uploaded (B)
252+
int, int, int, int]: # up rate (B/s), uploaded (B), dl rate (B/s), downloaded (B)
253253
global PNIC_BEFORE
254254
# Get current counters
255255
pnic_after = psutil.net_io_counters(pernic=True)

library/sensors/sensors_stub.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ def virtual_percent() -> float:
4646
return random.uniform(0, 100)
4747

4848
@staticmethod
49-
def virtual_used() -> int:
49+
def virtual_used() -> int: # In bytes
5050
return random.randint(300000000, 16000000000)
5151

5252
@staticmethod
53-
def virtual_free() -> int:
53+
def virtual_free() -> int: # In bytes
5454
return random.randint(300000000, 16000000000)
5555

5656

@@ -60,17 +60,17 @@ def disk_usage_percent() -> float:
6060
return random.uniform(0, 100)
6161

6262
@staticmethod
63-
def disk_used() -> int:
63+
def disk_used() -> int: # In bytes
6464
return random.randint(1000000000, 2000000000000)
6565

6666
@staticmethod
67-
def disk_free() -> int:
67+
def disk_free() -> int: # In bytes
6868
return random.randint(1000000000, 2000000000000)
6969

7070

7171
class Net(sensors.Net):
7272
@staticmethod
7373
def stats(if_name, interval) -> Tuple[
74-
int, int, int, int]: # dl rate (B/s), downloaded (B), up rate (B/s), uploaded (B)
74+
int, int, int, int]: # up rate (B/s), uploaded (B), dl rate (B/s), downloaded (B)
7575
return random.randint(1000000, 999000000), random.randint(1000000, 999000000), random.randint(
7676
1000000, 999000000), random.randint(1000000, 999000000)

0 commit comments

Comments
 (0)