From b4586155a796b6c8a15b36ecfc9b395dc91bb9fc Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 13 Oct 2021 15:54:06 +0200 Subject: [PATCH 1/8] add usage_request and usage_reply based on psutil --- ipykernel/kernelbase.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ceeebc23a..342ccd6fa 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,6 +16,7 @@ import time import uuid import warnings +import psutil try: # jupyter_client >= 5, use tz-aware now @@ -204,7 +205,7 @@ def _parent_header(self): 'apply_request', ] # add deprecated ipyparallel control messages - control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request'] + control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request', 'usage_request'] def __init__(self, **kwargs): super().__init__(**kwargs) @@ -856,10 +857,26 @@ async def debug_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) + reply_content['hello'] = 'hello' reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) + async def usage_request(self, stream, ident, parent): + content = parent['content'] + reply_content = self.do_debug_request(content) + if inspect.isawaitable(reply_content): + reply_content = await reply_content + reply_content = json_clean(reply_content) + reply_content['cpu_percent'] = psutil.cpu_percent() + reply_content['virtual_memory'] = psutil.virtual_memory() + reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) + reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent + reply_content['available_memory_percentage'] = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total + reply_msg = self.session.send(stream, 'usage_reply', reply_content, + parent, ident) + self.log.debug("%s", reply_msg) + async def do_debug_request(self, msg): raise NotImplementedError From 5602f4340b2ad0719d2ba19a8bb864b95afb7595 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:15:26 +0100 Subject: [PATCH 2/8] remove uneeded code --- ipykernel/kernelbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 342ccd6fa..471b6a4b3 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -857,7 +857,6 @@ async def debug_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_content['hello'] = 'hello' reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 95a65d5630e266902e54e2de0dd709331c038c37 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:36:31 +0100 Subject: [PATCH 3/8] guard on first cpu request returning None or 0.0 --- ipykernel/kernelbase.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 471b6a4b3..baa6a6a9f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -867,7 +867,11 @@ async def usage_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_content['cpu_percent'] = psutil.cpu_percent() + cpu_percent = psutil.cpu_percent() + # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent + # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. + if cpu_percent != None and cpu_percent != 0.0: + reply_content['cpu_percent'] = cpu_percent reply_content['virtual_memory'] = psutil.virtual_memory() reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent From 815e015d99414701f60e951b7fba463c56ef56a3 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:41:21 +0100 Subject: [PATCH 4/8] only return the memory usage as a dict --- ipykernel/kernelbase.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index baa6a6a9f..f72448f44 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -872,10 +872,7 @@ async def usage_request(self, stream, ident, parent): # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent != None and cpu_percent != 0.0: reply_content['cpu_percent'] = cpu_percent - reply_content['virtual_memory'] = psutil.virtual_memory() - reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) - reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent - reply_content['available_memory_percentage'] = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total + reply_content['virtual_memory'] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, 'usage_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 28172a1c0b9994c6af61468fac7665e923d115cb Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Sat, 4 Dec 2021 09:37:42 +0100 Subject: [PATCH 5/8] do not call debug_request on usage_request --- ipykernel/kernelbase.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index f72448f44..680a705ec 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -862,11 +862,7 @@ async def debug_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) async def usage_request(self, stream, ident, parent): - content = parent['content'] - reply_content = self.do_debug_request(content) - if inspect.isawaitable(reply_content): - reply_content = await reply_content - reply_content = json_clean(reply_content) + reply_content = {} cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. From 4c471547f71556bd4c84d360dcd2dbeadab03c6d Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 21 Dec 2021 08:32:46 +0100 Subject: [PATCH 6/8] Add kernel cpu and memory usage information --- ipykernel/kernelbase.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 680a705ec..0e1bbbc4c 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,7 +16,10 @@ import time import uuid import warnings -import psutil +try: + import psutil +except ImportError: + psutil = None try: # jupyter_client >= 5, use tz-aware now @@ -861,14 +864,37 @@ async def debug_request(self, stream, ident, parent): parent, ident) self.log.debug("%s", reply_msg) + # Taken from https://github.com/jupyter-server/jupyter-resource-usage/blob/e6ec53fa69fdb6de8e878974bcff006310658408/jupyter_resource_usage/metrics.py#L16 + def get_process_metric_value(self, process, name, attribute=None): + try: + # psutil.Process methods will either return... + metric_value = getattr(process, name)() + if attribute is not None: # ... a named tuple + return getattr(metric_value, attribute) + else: # ... or a number + return metric_value + # Avoid littering logs with stack traces + # complaining about dead processes + except BaseException: + return 0 + async def usage_request(self, stream, ident, parent): reply_content = {} + if psutil is None: + return reply_content + current_process = psutil.Process() + all_processes = [current_process] + current_process.children(recursive=True) + process_metric_value = lambda process, name, attribute: self.get_process_metric_value( + process, name, attribute + ) + reply_content['kernel_cpu'] = sum([process_metric_value(process, 'cpu_percent', None) for process in all_processes]) + reply_content['kernel_memory'] = sum([process_metric_value(process, 'memory_info', 'rss') for process in all_processes]) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent != None and cpu_percent != 0.0: - reply_content['cpu_percent'] = cpu_percent - reply_content['virtual_memory'] = dict(psutil.virtual_memory()._asdict()) + reply_content['host_cpu_percent'] = cpu_percent + reply_content['host_virtual_memory'] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, 'usage_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 4c437da92edce97664b1c1928b9fea51e4d64842 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Fri, 7 Jan 2022 11:34:44 +0100 Subject: [PATCH 7/8] return a None value instead of 0 on exception --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0e1bbbc4c..f162a1b6a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -876,7 +876,7 @@ def get_process_metric_value(self, process, name, attribute=None): # Avoid littering logs with stack traces # complaining about dead processes except BaseException: - return 0 + return None async def usage_request(self, stream, ident, parent): reply_content = {} From 1af59538152723e1795b885f4add63b86542564f Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Fri, 7 Jan 2022 12:12:10 +0100 Subject: [PATCH 8/8] remove unneeded lambda to get the metric values --- ipykernel/kernelbase.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index f162a1b6a..34119dc70 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -884,9 +884,7 @@ async def usage_request(self, stream, ident, parent): return reply_content current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) - process_metric_value = lambda process, name, attribute: self.get_process_metric_value( - process, name, attribute - ) + process_metric_value = self.get_process_metric_value reply_content['kernel_cpu'] = sum([process_metric_value(process, 'cpu_percent', None) for process in all_processes]) reply_content['kernel_memory'] = sum([process_metric_value(process, 'memory_info', 'rss') for process in all_processes]) cpu_percent = psutil.cpu_percent()