From e08cb0a681a37e4061357e0fbad819ba5a264807 Mon Sep 17 00:00:00 2001 From: shorti1996 Date: Fri, 4 Sep 2020 01:04:44 +0200 Subject: [PATCH 1/3] cpu_percentage Implement showing cpu_usage percentage --- ptop/interfaces/GUI.py | 24 ++++++++++++++---------- ptop/plugins/process_sensor.py | 26 ++++++++++++++++---------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/ptop/interfaces/GUI.py b/ptop/interfaces/GUI.py index c55f268..1550e2b 100644 --- a/ptop/interfaces/GUI.py +++ b/ptop/interfaces/GUI.py @@ -478,15 +478,15 @@ def update(self): # to keep things pre computed curtailed_processes_data = [] for proc in sorted_processes_data: - curtailed_processes_data.append("{0: <30} {1: >5}{6}{2: <10}{6}{3}{6}{4: >6.2f} % {6}{5}\ - ".format( (proc['name'][:25] + '...') if len(proc['name']) > 25 else proc['name'], - proc['id'], - proc['user'], - proc['time'], - proc['memory'], - proc['local_ports'], - " "*int(5*self.X_SCALING_FACTOR)) - ) + curtailed_processes_data.append("{0: <30}{1: >7}{7}{2: <10}{7}{3}{7}{4}{7}{5}{7}{6}\ + ".format((proc['name'][:25] + '...') if len(proc['name']) > 25 else proc['name'], + proc['id'], + proc['user'], + proc['time'], + self.format_percentage(proc['cpu']), + self.format_percentage(proc['memory']), + proc['local_ports'], + " " * int(5 * self.X_SCALING_FACTOR))) if not self.processes_table.entry_widget.is_filtering_on(): self.processes_table.entry_widget.values = curtailed_processes_data # Set the processes data dictionary to uncurtailed processes data @@ -503,6 +503,10 @@ def update(self): except KeyError: self._logger.info("Some of the stats reading failed",exc_info=True) + @staticmethod + def format_percentage(float_value): + return f'{float_value: >6.2f}'.rjust(2, ' ') + ' %' + def draw(self): # Setting the main window form self.window = WindowForm(parentApp=self, @@ -610,7 +614,7 @@ def draw(self): PROCESSES_INFO_WIDGET_REL_Y+PROCESSES_INFO_WIDGET_HEIGHT) ) self.processes_table = self.window.add(MultiLineActionWidget, - name="Processes ( name - PID - user - age - memory - system_ports )", + name="Processes ( name - PID - user - age - cpu - memory - system_ports )", relx=PROCESSES_INFO_WIDGET_REL_X, rely=PROCESSES_INFO_WIDGET_REL_Y, max_height=PROCESSES_INFO_WIDGET_HEIGHT, diff --git a/ptop/plugins/process_sensor.py b/ptop/plugins/process_sensor.py index 0569c21..9d8158d 100644 --- a/ptop/plugins/process_sensor.py +++ b/ptop/plugins/process_sensor.py @@ -22,6 +22,8 @@ def __init__(self,**kwargs): self.currentValue['table'] = [] self._currentSystemUser = getpass.getuser() self._logger = logging.getLogger(__name__) + # processes once retrieved from psutil are stored in a list to make cpu percentage measurement possible + self._process_list = [] def format_time(self, d): ret = '{0} day{1} '.format(d.days, ' s'[d.days > 1]) if d.days else '' @@ -31,7 +33,7 @@ def format_time(self, d): s -= m * 60 return ret + '{0:2d}:{1:02d}:{2:02d}'.format(h, m, s) - # overriding the upate method + # overriding the update method def update(self): # flood the data thread_count = 0 #keep track number of threads @@ -50,7 +52,7 @@ def update(self): because getting further process info for root processes as a normal user will give Permission Denied #10 ''' - p = psutil.Process(proc.pid) + p = self.retrieve_process_by_pid(proc.pid) proc_info['user'] = p.username() try: if ((proc_info['user'] == self._currentSystemUser) or (self._currentSystemUser in PRIVELAGED_USERS)) \ @@ -75,16 +77,12 @@ def update(self): SYSTEM_USERS.append(proc_info['user']) except: ''' - In case ptop does not have privelages to access info for some of the processes + In case ptop does not have privileges to access info for some of the processes just log them and don't show them in the processes table ''' - self._logger.info('''Not able to get info for process {0} with status {1} invoked by user {2}, ptop - is invoked by the user {3}'''.format(str(p.pid), - p.status(), - p.username(), - self._currentSystemUser - ), - exc_info=True) + self._logger.info(f"""Not able to get info for process {p.pid} with status {p.status()} invoked by user {p.username()}, ptop + is invoked by the user {self._currentSystemUser}""", + exc_info=True) # padding time time_len = max((len(proc['time']) for proc in proc_info_list)) @@ -96,5 +94,13 @@ def update(self): self.currentValue['text']['running_processes'] = str(proc_count) self.currentValue['text']['running_threads'] = str(thread_count) + def retrieve_process_by_pid(self, pid): + found_processes = [x for x in self._process_list if x.pid == pid] + process = found_processes[0] if len(found_processes) > 0 else psutil.Process(pid) + if not found_processes and process: + self._process_list.append(process) + return process + + # make the process sensor less frequent as it takes more time to fetch info process_sensor = ProcessSensor(name='Process',sensorType='table',interval=1) \ No newline at end of file From b1a4c5a9427ab028898c2177fc54abaeb92ba4f3 Mon Sep 17 00:00:00 2001 From: shorti1996 Date: Fri, 4 Sep 2020 01:45:58 +0200 Subject: [PATCH 2/3] cpu_percentage Implement CPU sorting, fix sorting, implement reverse sorting. --- ptop/interfaces/GUI.py | 75 +++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/ptop/interfaces/GUI.py b/ptop/interfaces/GUI.py index 1550e2b..68dbdaa 100644 --- a/ptop/interfaces/GUI.py +++ b/ptop/interfaces/GUI.py @@ -2,6 +2,7 @@ ''' Graphical User Interface for ptop ''' +from enum import Enum import npyscreen, math, drawille import psutil, logging, weakref, sys @@ -9,10 +10,18 @@ from ptop.constants import SYSTEM_USERS, SUPPORTED_THEMES +class SortOption(Enum): + PROCESS_RELEVANCE = 0 + MEMORY = 1 + MEMORY_REVERSED = 2 + TIME = 3 + TIME_REVERSED = 4 + CPU = 5 + CPU_REVERSED = 6 + + # global flags defining actions, would like them to be object vars -TIME_SORT = False -MEMORY_SORT = False -PROCESS_RELEVANCE_SORT = True +CURRENT_SORTING = SortOption.PROCESS_RELEVANCE PREVIOUS_TERMINAL_WIDTH = None PREVIOUS_TERMINAL_HEIGHT = None @@ -119,6 +128,7 @@ def __init__(self,*args,**kwargs): self.add_handlers({ "^N" : self._sort_by_memory, "^T" : self._sort_by_time, + "^U" : self._sort_by_cpu, "^K" : self._kill_process, "^Q" : self._quit, "^R" : self._reset, @@ -171,24 +181,32 @@ def _get_list_of_open_files(self,process_pid): def _sort_by_time(self,*args,**kwargs): # fuck .. that's why NPSManaged was required, i.e you can access the app instance within widgets self._logger.info("Sorting the process table by time") - global TIME_SORT,MEMORY_SORT - MEMORY_SORT = False - TIME_SORT = True - PROCESS_RELEVANCE_SORT = False + global CURRENT_SORTING + if CURRENT_SORTING == SortOption.TIME_REVERSED: + CURRENT_SORTING = SortOption.TIME + else: + CURRENT_SORTING = SortOption.TIME_REVERSED + + def _sort_by_cpu(self,*args,**kwargs): + self._logger.info("Sorting the process table by cpu") + global CURRENT_SORTING + if CURRENT_SORTING == SortOption.CPU_REVERSED: + CURRENT_SORTING = SortOption.CPU + else: + CURRENT_SORTING = SortOption.CPU_REVERSED def _sort_by_memory(self,*args,**kwargs): self._logger.info("Sorting the process table by memory") - global TIME_SORT,MEMORY_SORT - TIME_SORT = False - MEMORY_SORT = True - PROCESS_RELEVANCE_SORT = False + global CURRENT_SORTING + if CURRENT_SORTING == SortOption.MEMORY_REVERSED: + CURRENT_SORTING = SortOption.MEMORY + else: + CURRENT_SORTING = SortOption.MEMORY_REVERSED def _reset(self,*args,**kwargs): self._logger.info("Resetting the process table") - global TIME_SORT, MEMORY_SORT - TIME_SORT = False - MEMORY_SORT = False - PROCESS_RELEVANCE_SORT = True + global CURRENT_SORTING + CURRENT_SORTING = SortOption.PROCESS_RELEVANCE self._filtering_flag = False def _do_process_filtering_work(self,*args,**kwargs): @@ -462,18 +480,23 @@ def update(self): self._processes_data = self.statistics['Process']['table'] # check sorting flags - if MEMORY_SORT: - sorted_processes_data = sorted(self._processes_data,key=lambda k:k['memory'],reverse=True) - self._logger.info("Memory sorting done for process table") - elif TIME_SORT: - sorted_processes_data = sorted(self._processes_data,key=lambda k:k['rawtime'],reverse=True) - self._logger.info("Time sorting done for process table") - elif PROCESS_RELEVANCE_SORT: - sorted_processes_data = sorted(self._processes_data,key=lambda k:k['rawtime']) - self._logger.info("Sorting on the basis of relevance") + if CURRENT_SORTING == SortOption.MEMORY: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['memory'], reverse=False) + if CURRENT_SORTING == SortOption.MEMORY_REVERSED: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['memory'], reverse=True) + elif CURRENT_SORTING == SortOption.TIME: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['rawtime'], reverse=False) + elif CURRENT_SORTING == SortOption.TIME_REVERSED: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['rawtime'], reverse=True) + elif CURRENT_SORTING == SortOption.CPU: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['cpu'], reverse=False) + elif CURRENT_SORTING == SortOption.CPU_REVERSED: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['cpu'], reverse=True) + elif CURRENT_SORTING == SortOption.PROCESS_RELEVANCE: + sorted_processes_data = sorted(self._processes_data, key=lambda k: k['rawtime']) else: sorted_processes_data = self._processes_data - self._logger.info("Resetting the sorting behavior") + self._logger.info(f"Process table sorted by {CURRENT_SORTING.name}") # to keep things pre computed curtailed_processes_data = [] @@ -637,7 +660,7 @@ def draw(self): relx=ACTIONS_WIDGET_REL_X, rely=ACTIONS_WIDGET_REL_Y ) - self.actions.value = "^K:Kill\t\t^N:Memory Sort\t\t^T:Time Sort\t\t^R:Reset\t\tg:Top\t\t^Q:Quit\t\t^F:Filter\t\t^L:Process Info" + self.actions.value = "^K:Kill\t\t^N:Memory Sort\t\t^T:Time Sort\t\t^U:CPU Sort\t\t^R:Reset\t\tg:Top\t\t^Q:Quit\t\t^F:Filter\t\t^L:Process Info" self.actions.display() self.actions.editable = False From b159f253256d98cd1e960e8963030bfe6704082e Mon Sep 17 00:00:00 2001 From: shorti1996 Date: Mon, 7 Sep 2020 00:04:59 +0200 Subject: [PATCH 3/3] cpu_percentage process_sensor: clean process list each X update ticks --- ptop/plugins/process_sensor.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/ptop/plugins/process_sensor.py b/ptop/plugins/process_sensor.py index 9d8158d..7628b7d 100644 --- a/ptop/plugins/process_sensor.py +++ b/ptop/plugins/process_sensor.py @@ -13,7 +13,9 @@ class ProcessSensor(Plugin): - def __init__(self,**kwargs): + PROCESS_LIST_CLEANUP_TICKS = 100 + + def __init__(self, **kwargs): super(ProcessSensor,self).__init__(**kwargs) # there will be two parts of the returned value, one will be text and other graph # there can be many text (key,value) pairs to display corresponding to each key @@ -23,7 +25,8 @@ def __init__(self,**kwargs): self._currentSystemUser = getpass.getuser() self._logger = logging.getLogger(__name__) # processes once retrieved from psutil are stored in a list to make cpu percentage measurement possible - self._process_list = [] + self._process_list = {} + self._tick = 0 def format_time(self, d): ret = '{0} day{1} '.format(d.days, ' s'[d.days > 1]) if d.days else '' @@ -94,11 +97,19 @@ def update(self): self.currentValue['text']['running_processes'] = str(proc_count) self.currentValue['text']['running_threads'] = str(thread_count) + self.tick(proc_info_list) + + def tick(self, proc_info_list): + self._tick = (self._tick + 1) % ProcessSensor.PROCESS_LIST_CLEANUP_TICKS + if self._tick == 0: + self._process_list = {key: value for (key, value) in self._process_list.items() if key in [y['id'] for y in proc_info_list]} + def retrieve_process_by_pid(self, pid): - found_processes = [x for x in self._process_list if x.pid == pid] - process = found_processes[0] if len(found_processes) > 0 else psutil.Process(pid) - if not found_processes and process: - self._process_list.append(process) + try: + process = self._process_list[pid] + except KeyError: + process = psutil.Process(pid) + self._process_list[pid] = process return process