|
| 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 |
0 commit comments