From 02f071a6814fd369fb3da705eef2fd55bee3694b Mon Sep 17 00:00:00 2001 From: Alexey Berezhok Date: Mon, 25 May 2026 13:31:04 +0300 Subject: [PATCH 1/3] Added fix for showing temperature for AMD Ryzen 9 5950X and similar --- .gitignore | 3 ++- sysmontask/cpu.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9494459..ebad183 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ exp.py exp.glade exp.glade~ -netSidepane.glade~ +netSidepane.glade~ disk.glade~ taskManager.glade~ taskManger.py @@ -45,3 +45,4 @@ var/ .vscode/ .buildconfig +.zed diff --git a/sysmontask/cpu.py b/sysmontask/cpu.py index 0c76b63..60d35f2 100644 --- a/sysmontask/cpu.py +++ b/sysmontask/cpu.py @@ -201,6 +201,9 @@ def cpuUpdate(self): if lis.label=='Tdie': self.cpuTempLabelValue.set_text('{0} °C'.format(int(lis.current))) break + if lis.label=='Tccd1': + self.cpuTempLabelValue.set_text('{0} °C'.format(int(lis.current))) + break elif 'zenpower' in temperatures_list: for lis in temperatures_list['zenpower']: if lis.label=='Tdie': From 3022933e7fd4954c0e17f261ef8c5d14cee63886 Mon Sep 17 00:00:00 2001 From: Alexey Berezhok Date: Fri, 29 May 2026 03:10:15 +0300 Subject: [PATCH 2/3] Added CPU temperature detect for AMD Ryzen 3200G --- sysmontask/cpu.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sysmontask/cpu.py b/sysmontask/cpu.py index 60d35f2..f8817da 100644 --- a/sysmontask/cpu.py +++ b/sysmontask/cpu.py @@ -204,6 +204,9 @@ def cpuUpdate(self): if lis.label=='Tccd1': self.cpuTempLabelValue.set_text('{0} °C'.format(int(lis.current))) break + if lis.label=='Tctl': + self.cpuTempLabelValue.set_text('{0} °C'.format(int(lis.current))) + break elif 'zenpower' in temperatures_list: for lis in temperatures_list['zenpower']: if lis.label=='Tdie': From 2d1521c820d8aef974ee78bccea3a60acb716a5b Mon Sep 17 00:00:00 2001 From: Alexey Berezhok Date: Thu, 4 Jun 2026 17:14:48 +0300 Subject: [PATCH 3/3] - Try to add to show amd gpu info support with sensors and glxinfo --- sysmontask/gpu.py | 351 ++++++++++++++++++++++++++++++++++----- sysmontask/sidepane.py | 6 +- sysmontask/sysmontask.py | 23 ++- 3 files changed, 326 insertions(+), 54 deletions(-) diff --git a/sysmontask/gpu.py b/sysmontask/gpu.py index 0e28689..7e7ac8c 100644 --- a/sysmontask/gpu.py +++ b/sysmontask/gpu.py @@ -5,6 +5,7 @@ from gi.repository import Gtk as g from os import popen from xml.etree.ElementTree import fromstring +import re try: from gi_composites import GtkTemplate @@ -69,13 +70,26 @@ def givedata(self,secondself): self.gpuvramArray=secondself.gpuVramArray self.gpuencodingArray=secondself.gpuEncodingArray self.gpudecodingArray=secondself.gpuDecodingArray - self.gputotalvram=int(secondself.totalvram[:-3]) + # Handle case where totalvram is "NA" or improperly formatted + if secondself.totalvram and secondself.totalvram.strip() != 'NA' and len(secondself.totalvram) > 3: + try: + self.gputotalvram=int(secondself.totalvram[:-3]) + except ValueError: + # If parsing fails, set to 0 or extract numeric value from string + import re + match = re.search(r'([\d.]+)', secondself.totalvram) + if match: + self.gputotalvram=int(float(match.group(1))) + else: + self.gputotalvram=0 + else: + self.gputotalvram=0 self.secondself=secondself @GtkTemplate.Callback def gpuutildrawarea_draw(self,dr,cr): """ - Function Binding(for draw signal) for gpu utilization drawing area. + Function Binding(for draw signal) for gpu utilisation drawing area. This function draw the GPU Utilisation curves upon called by the queue of request generated in the main *updator* function. @@ -304,7 +318,13 @@ def gpuvramdrawarea_draw(self,dr,cr): w=self.gpuvramdrawarea.get_allocated_width() h=self.gpuvramdrawarea.get_allocated_height() - scalingfactor=h/self.gputotalvram + + # Prevent division by zero - use default value if gputotalvram is 0 or missing + vram_total = getattr(self, 'gputotalvram', 1) + if vram_total == 0: + vram_total = 1 + + scalingfactor=h/vram_total # print(self.gputotalvram) #creating outer rectangle cr.set_source_rgba(*rectangle_color,1) @@ -362,6 +382,121 @@ def gpuvramdrawarea_draw(self,dr,cr): +# Helper function to parse sensors output for AMD GPU data +def parse_amd_sensors_output(sensors_output): + """ + Parse sensors output to extract AMD GPU information. + + Returns: + dict: Dictionary containing fan_rpm, gpu_temp, current_power, max_power + """ + result = { + 'fan_rpm': 0, + 'gpu_temp': 'NA', + 'current_power': 0, + 'max_power': 1 # Default max power if not found + } + + lines = sensors_output.split('\n') + in_amdgpu_section = False + + for line in lines: + + if 'amdgpu-pci' not in line.lower() and not in_amdgpu_section: + continue + + in_amdgpu_section = True + + if line.strip() == '': + in_amdgpu_section = False + break + + # Parse fan1 RPM + if 'fan1' in line.lower(): + match = re.search(r'(\d+)\s*RPM', line) + if match: + result['fan_rpm'] = int(match.group(1)) + + # Parse edge temperature (GPU temp) + if 'edge' in line.lower() or line.startswith('edge:'): + match = re.search(r'\+(\d+\.?\d*)°?C', line) + if match: + result['gpu_temp'] = f"+{match.group(1)}°C" + + # Parse PPT power (current and max) + if 'PPT:' in line: + match = re.search(r'PPT:\s*([\d.]+)\s*W\s*\((cap\s*=\s*)?([\d.]+)\s*W', line, re.IGNORECASE) + if match: + result['current_power'] = float(match.group(1)) + result['max_power'] = float(match.group(3)) + + return result + +# Helper function to extract GPU name from glxinfo output +def extract_gpu_name_from_glxinfo(glxinfo_output): + """ + Extract GPU/device name from glxinfo Extended renderer info. + + Returns: + str: GPU name or "NA" if not found + """ + gpu_section = False + for line in glxinfo_output.splitlines(): + # Detect the start of the Extended renderer info section + if line.strip().startswith('Extended renderer info (GLX_MESA_query_renderer):') and not gpu_section: + gpu_section = True + continue + if not gpu_section: + continue + # End of the section is indicated by a blank line or a line that doesn't start with whitespace + if line.strip() == '' or not line.startswith(' '): + break + # Extract the device line + stripped = line.strip() + if stripped.startswith('Device:'): + device_match = re.search(r'Device:\s*([^\(]+)', stripped) + if device_match: + return device_match.group(1).strip() + return "NA" + +# Helper function to extract VRAM info from glxinfo output +def extract_vram_info_from_glxinfo(glxinfo_output): + """ + Extract VRAM information from glxinfo Memory info sections. + + Returns: + tuple: (total_vram_mb, available_dedicated_memory_mb) + """ + # First try GL_NVX_gpu_memory_info block (preferred) + gpu_section = False + dedicated_match = None + available_match = None + for line in glxinfo_output.splitlines(): + # Detect the start of the Extended renderer info section + if line.strip().startswith('Memory info (GL_NVX_gpu_memory_info):') and not gpu_section: + gpu_section = True + continue + if not gpu_section: + continue + # End of the section is indicated by a blank line or a line that doesn't start with whitespace + if line.strip() == '' or not line.startswith(' '): + break + # Extract the device line + stripped = line.strip() + if 'Dedicated video memory:' in stripped: + dedicated_match = re.search(r'Dedicated video memory:\s*([\d.]+)\s*MB', stripped) + + if 'Currently available dedicated video memory:' in stripped: + available_match = re.search(r'Currently available dedicated video memory:\s*([\d.]+)\s*MB', stripped) + + if dedicated_match and available_match: + total_vram = int(float(dedicated_match.group(1))) + used_dedicated = float(available_match.group(1)) + return f"{total_vram} MiB", f"{used_dedicated:.0f} MiB" + + return "0 MiB", "0 MiB" + + def gpuinit(self): ##logic to determine the number of gpus but for now i just focusing on one gpu self.isNvidiagpu=1 @@ -395,77 +530,205 @@ def gpuinit(self): except Exception as e: print('no nvidia gpu found',e) self.isNvidiagpu=0 + + self.isAMDgpu=0 + + try: + # Check for AMD GPU if Nvidia not found + # First check for amdgpu-pci in sensors + sensors_out = popen('sensors 2>/dev/null') + sensors_output = sensors_out.read() + sensors_out.close() + + if 'amdgpu-pci' in sensors_output: + # Get glxinfo for GPU name and VRAM + glxinfo_out = popen('glxinfo 2>/dev/null') + glxinfo_output = glxinfo_out.read() + glxinfo_out.close() + + # Extract device info from glxinfo + if 'Vendor: AMD' in glxinfo_output or 'Radeon' in glxinfo_output or 'radeonsi' in glxinfo_output: + self.isAMDgpu=1 + self.gpuWidget=gpuTabWidget() + self.performanceStack.add_titled(self.gpuWidget,f'page{self.stack_counter}','GPU') + self.gpuName = extract_gpu_name_from_glxinfo(glxinfo_output) + + # Get VRAM info + total_vram, vram_usage_str = extract_vram_info_from_glxinfo(glxinfo_output) + self.totalvram = total_vram + self.gpuWidget.gpuvramlabelvalue.set_text(total_vram) + # Initialize gputotalvram as int (required for drawing) + if total_vram and str(total_vram).strip() != '0MiB' and len(str(total_vram)) > 3: + try: + self.gputotalvram = int(float(total_vram[:-3])) + except ValueError: + self.gputotalvram = 0 + + temp_result = parse_amd_sensors_output(sensors_output) + + # Set GPU name and VRAM + self.gpuWidget.gpuinfolabel.set_text(self.gpuName) + + # For AMD, we don't have driver/cuda versions, set to NA + self.gpuWidget.gpudriverlabelvalue.set_text("NA") + self.gpuWidget.gpucudalabelvalue.set_text("NA") + + # Set clock speeds - AMD doesn't expose this via sensors in same way + # We'll leave these as NA for now + self.gpuWidget.gpumaxspeedlabelvalue.set_text("NA") + self.gpuWidget.gpuvrammaxspeedlabelvalue.set_text("NA") + + # Set initial temperature and utilization to sensor values + gpu_temp = temp_result['gpu_temp'] + self.gpuWidget.gputemplabelvalue.set_text(gpu_temp) + + self.gpuWidget.gpushaderspeedlabelvalue.set_text("NA") + self.gpuWidget.gpuvramspeedlabelvalue.set_text("NA") + + # Calculate utilization from PPT power + if temp_result['current_power'] > 0 and temp_result['max_power'] > 0: + gpu_util_pct = int((temp_result['current_power'] / temp_result['max_power']) * 100) + self.gpuutil = str(gpu_util_pct) + ' %' + self.gpuWidget.gpuutilisationlabelvalue.set_text(f"{gpu_util_pct} %") + else: + self.gpuutil = "0 %" + self.gpuWidget.gpuutilisationlabelvalue.set_text("0 %") + + # For lookup of devices and its assigned stack page numbers + self.device_stack_page_lookup[self.gpuName] = self.stack_counter + self.stack_counter += 1 + + self.gpuWidget.givedata(self) + except Exception as e: + print('no amd gpu found',e) + self.isAMDgpu=0 def gpuUpdate(self): try: - p=popen('nvidia-smi -q -x') - xmlout=p.read() - p.close() - gpuinfoRoot=fromstring(xmlout) - self.vramused=gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text - self.gpuutil=gpuinfoRoot.find('gpu').find('utilization').find('gpu_util').text - self.gpuWidget.gpuutilisationlabelvalue.set_text(self.gpuutil) - self.gpuWidget.gpuvramusagelabelvalue.set_text(f'{self.vramused[:-3]}/{self.totalvram}') - - gpu_temp=gpuinfoRoot.find('gpu').find('temperature').find('gpu_temp').text - if gpu_temp[-1]=='C': - gpu_temp =f'{gpu_temp[:-1]}°C' - - self.gpuWidget.gputemplabelvalue.set_text(gpu_temp) - self.gpuWidget.gpushaderspeedlabelvalue.set_text(gpuinfoRoot.find('gpu').find('clocks').find('graphics_clock').text) - self.gpuWidget.gpuvramspeedlabelvalue.set_text(gpuinfoRoot.find('gpu').find('clocks').find('mem_clock').text) - - ############ int conv bug solve ###################### - gpu_enc=gpuinfoRoot.find('gpu').find('utilization').find('encoder_util').text - try: - gpu_enc=int(gpu_enc[:-1]) - except Exception: - gpu_enc=0 - - gpu_dec=gpuinfoRoot.find('gpu').find('utilization').find('decoder_util').text + if self.isNvidiagpu: + # Nvidia GPU update path (existing code) + p=popen('nvidia-smi -q -x') + xmlout=p.read() + p.close() + gpuinfoRoot=fromstring(xmlout) + self.vramused=gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text + self.gpuutil=gpuinfoRoot.find('gpu').find('utilization').find('gpu_util').text + self.gpuWidget.gpuutilisationlabelvalue.set_text(self.gpuutil) + self.gpuWidget.gpuvramusagelabelvalue.set_text(f'{self.vramused[:-3]}/{self.totalvram}') + + gpu_temp=gpuinfoRoot.find('gpu').find('temperature').find('gpu_temp').text + if gpu_temp[-1]=='C': + gpu_temp =f'{gpu_temp[:-1]}°C' + + self.gpuWidget.gputemplabelvalue.set_text(gpu_temp) + self.gpuWidget.gpushaderspeedlabelvalue.set_text(gpuinfoRoot.find('gpu').find('clocks').find('graphics_clock').text) + self.gpuWidget.gpuvramspeedlabelvalue.set_text(gpuinfoRoot.find('gpu').find('clocks').find('mem_clock').text) + + ############ int conv bug solve ###################### + gpu_enc=gpuinfoRoot.find('gpu').find('utilization').find('encoder_util').text + try: + gpu_enc=int(gpu_enc[:-1]) + except Exception: + gpu_enc=0 - try: - gpu_dec=int(gpu_dec[:-1]) - except Exception: - gpu_dec=0 + gpu_dec=gpuinfoRoot.find('gpu').find('utilization').find('decoder_util').text + try: + gpu_dec=int(gpu_dec[:-1]) + except Exception: + gpu_dec=0 + + elif self.isAMDgpu: + # AMD GPU update path - use sensors + sensors_out = popen('sensors 2>/dev/null') + sensors_output = sensors_out.read() + sensors_out.close() + + glxinfo_out = popen('glxinfo 2>/dev/null') + glxinfo_output = glxinfo_out.read() + glxinfo_out.close() + + # Parse temperature + temp_result = parse_amd_sensors_output(sensors_output) + + total_vram, vram_usage_str = extract_vram_info_from_glxinfo(glxinfo_output) + + # Update labels with sensor data + self.gpuWidget.gputemplabelvalue.set_text(temp_result['gpu_temp']) + + # Calculate utilization from PPT power + if temp_result['current_power'] > 0 and temp_result['max_power'] > 0: + gpu_util_pct = int((temp_result['current_power'] / temp_result['max_power']) * 100) + self.gpuutil = str(gpu_util_pct) + ' %' + self.gpuWidget.gpuutilisationlabelvalue.set_text(f"{gpu_util_pct} %") + else: + self.gpuutil = "0 %" + self.gpuWidget.gpuutilisationlabelvalue.set_text("0 %") + + # Update VRAM usage from glxinfo (static for now, as it changes with OpenGL context) + # For AMD, we can't reliably get current VRAM usage without glxinfo with specific context + if hasattr(self, 'totalvram') and self.totalvram and str(self.totalvram).strip() != '0 MiB': + self.gpuWidget.gpuvramusagelabelvalue.set_text(f"{vram_usage_str[:-3]}/{self.totalvram}") + + # Set clocks to NA for AMD (not exposed in sensors) + self.gpuWidget.gpushaderspeedlabelvalue.set_text("NA") + self.gpuWidget.gpuvramspeedlabelvalue.set_text("NA") + if self.update_graph_direction: self.gpuUtilArray.pop(0) try: - self.gpuUtilArray.append(int(self.gpuutil[:-1])) + # Extract percentage value from utilization string + util_val = int(self.gpuutil[:-1]) if '%' in self.gpuutil else int(self.gpuutil) + self.gpuUtilArray.append(util_val) except Exception: self.gpuUtilArray.append(0) self.gpuVramArray.pop(0) - try: - self.gpuVramArray.append(int(gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text[:-3])) - except Exception: - self.gpuVramArray.append(0) + # Only get VRAM data if we're on Nvidia (have gpuinfoRoot); otherwise use default + if 'gpuinfoRoot' in dir(): + try: + vram_str = gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text[:-3] + self.gpuVramArray.append(int(vram_str)) + except Exception: + self.gpuVramArray.append(0) + else: + # For AMD, use half of total VRAM as placeholder + try: + vram_usage = int(float(vram_usage_str[:-3])) + except ValueError: + vram_usage = 0 + + self.gpuVramArray.append(vram_usage) self.gpuEncodingArray.pop(0) - self.gpuEncodingArray.append(gpu_enc) + self.gpuEncodingArray.append(0) # AMD doesn't have encoder util in same format self.gpuDecodingArray.pop(0) - self.gpuDecodingArray.append(gpu_dec) + self.gpuDecodingArray.append(0) # AMD doesn't have decoder util in same format else: self.gpuUtilArray.pop() try: - self.gpuUtilArray.insert(0,int(self.gpuutil[:-1])) + util_val = int(self.gpuutil[:-1]) if '%' in self.gpuutil else int(self.gpuutil) + self.gpuUtilArray.insert(0,util_val) except Exception: self.gpuUtilArray.insert(0,0) self.gpuVramArray.pop() try: - self.gpuVramArray.insert(0,int(gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text[:-3])) + if 'gpuinfoRoot' in dir(): + vram_str = gpuinfoRoot.find('gpu').find('fb_memory_usage').find('used').text[:-3] + else: + vram_str = vram_usage_str[:-3] + self.gpuVramArray.insert(0,int(vram_str)) except Exception: self.gpuVramArray.insert(0,0) self.gpuEncodingArray.pop() - self.gpuEncodingArray.insert(0,gpu_enc) + self.gpuEncodingArray.insert(0,0) self.gpuDecodingArray.pop() - self.gpuDecodingArray.insert(0,gpu_dec) + self.gpuDecodingArray.insert(0,0) self.gpuWidget.givedata(self) except Exception as e: - print(f"some error in gpu updata: {e}") \ No newline at end of file + print(f"some error in gpu updata: {e}") diff --git a/sysmontask/sidepane.py b/sysmontask/sidepane.py index fbcd41b..d247566 100644 --- a/sysmontask/sidepane.py +++ b/sysmontask/sidepane.py @@ -580,7 +580,7 @@ def sidepaneinit(self): self.stack_switcher_buttons[button_counter]=self.netSidepaneWidgetList[i].net_switcher_button button_counter+=1 - if(self.isNvidiagpu==1): + if(self.isNvidiagpu==1 or self.isAMDgpu==1): self.gpuSidePaneWidget=gpuSidepaneWidget() self.sidepaneBox.pack_start(self.gpuSidePaneWidget,True,True,0) self.gpuSidePaneWidget.gpusidepanetextlabel.set_text(f'{self.gpuName.split()[-2]}{self.gpuName.split()[-1]}') @@ -618,9 +618,9 @@ def sidePaneUpdate(self): except Exception as e: print(f"some error in netsidepane update {e}") - if(self.isNvidiagpu==1): + if(self.isNvidiagpu==1 or self.isAMDgpu==1): try: self.gpuSidePaneWidget.gpusidepanelabelvalue.set_text(self.gpuutil) self.gpuSidePaneWidget.givedata(self) except Exception as e: - print(f"some error in gpusidepane update {e}") \ No newline at end of file + print(f"some error in gpusidepane update {e}") diff --git a/sysmontask/sysmontask.py b/sysmontask/sysmontask.py index f194e3a..6ef0420 100755 --- a/sysmontask/sysmontask.py +++ b/sysmontask/sysmontask.py @@ -282,7 +282,7 @@ def post_init(self): 'cpu':'cpu', 'memory':'memory', } - if self.isNvidiagpu: + if self.isNvidiagpu or self.isAMDgpu: self.grouping_for_color_profile[self.gpuName]='gpu' for i in self.disklist: self.grouping_for_color_profile[i]='disk' @@ -539,9 +539,18 @@ def on_refresh_activate(self,menuitem,data=None): self.stack_counter=2 # Destroying the all the except CPU and Memory - if(self.isNvidiagpu==1): - g.Widget.destroy(self.gpuWidget) - g.Widget.destroy(self.gpuSidePaneWidget) + if(self.isNvidiagpu==1 or self.isAMDgpu==1): + if hasattr(self, 'gpuWidget'): + g.Widget.destroy(self.gpuWidget) + # Remove GPU from performanceStack before destroying + try: + self.performanceStack.remove(self.gpuWidget) + except Exception as e: + pass + if hasattr(self, 'gpuSidePaneWidget'): + g.Widget.destroy(self.gpuSidePaneWidget) + if hasattr(self, 'gpuSwitcherButton'): + g.Widget.destroy(self.gpuSwitcherButton) for i in range(0,self.numOfDisks): g.Widget.destroy(self.diskWidgetList[i]) g.Widget.destroy(self.diskSidepaneWidgetList[i]) @@ -685,7 +694,7 @@ def updater(self): self.disktabUpdate() if len(self.netNameList)!=0: self.netTabUpdate() - if(self.isNvidiagpu==1): + if(self.isNvidiagpu==1 or self.isAMDgpu==1): self.gpuTabUpdate() self.sidepaneUpdate() @@ -704,7 +713,7 @@ def updater(self): for i in range(0,self.numOfNets): g.Widget.queue_draw(self.netWidgetList[i].netdrawarea) - if(self.isNvidiagpu==1): + if(self.isNvidiagpu==1 or self.isAMDgpu==1): g.Widget.queue_draw(self.gpuWidget.gpuutildrawarea) g.Widget.queue_draw(self.gpuWidget.gpuvramdrawarea) g.Widget.queue_draw(self.gpuWidget.gpuencodingdrawarea) @@ -718,7 +727,7 @@ def updater(self): g.Widget.queue_draw(self.diskSidepaneWidgetList[i].disksidepanedrawarea) for i in range(self.numOfNets): g.Widget.queue_draw(self.netSidepaneWidgetList[i].netsidepanedrawarea) - if(self.isNvidiagpu==1): + if(self.isNvidiagpu==1 or self.isAMDgpu==1): g.Widget.queue_draw(self.gpuSidePaneWidget.gpusidepanedrawarea) # Returning True to run periodically