From 6a8d74e9d2060c8097c6d460c98dc38509fa9d0a Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Tue, 19 Mar 2024 11:03:41 +0100 Subject: [PATCH] refactor lowe usage percentage --- Dockerfile | 2 +- docker/activity_monitor.py | 39 +++++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) mode change 100644 => 100755 docker/activity_monitor.py diff --git a/Dockerfile b/Dockerfile index 40fcb66..4831188 100644 --- a/Dockerfile +++ b/Dockerfile @@ -93,7 +93,7 @@ ENV JP_LSP_VIRTUAL_DIR="/home/${NB_USER}/.virtual_documents" COPY --chown=$NB_UID:$NB_GID docker /docker RUN chmod +x /docker/activity.py \ - && chmod +x /docker/kernel_checker.py + && chmod +x /docker/activity_monitor.py RUN echo 'export PATH="/home/${NB_USER}/.venv/bin:$PATH"' >> "/home/${NB_USER}/.bashrc" diff --git a/docker/activity_monitor.py b/docker/activity_monitor.py old mode 100644 new mode 100755 index fd7874d..7919bfc --- a/docker/activity_monitor.py +++ b/docker/activity_monitor.py @@ -13,6 +13,7 @@ import psutil import requests import tornado +import subprocess from concurrent.futures import ThreadPoolExecutor, as_completed from contextlib import suppress @@ -22,7 +23,7 @@ CHECK_INTERVAL_S: Final[float] = 5 CPU_USAGE_MONITORING_INTERVAL_S: Final[float] = 1 -THRESHOLD_CPU_USAGE: Final[float] = 20 # percent in range [0, 100] +THRESHOLD_CPU_USAGE: Final[float] = 5 # percent in range [0, 100] class JupyterKernelMonitor: @@ -51,6 +52,7 @@ def are_kernels_busy(self) -> bool: class CPUUsageMonitor: def __init__(self, threshold: float): self.threshold = threshold + def _get_children_processes(self, pid) -> list[psutil.Process]: try: return psutil.Process(pid).children(recursive=True) @@ -62,7 +64,7 @@ def _get_brother_processes(self) -> list[psutil.Process]: # ASSUMPTIONS: # - `CURRENT_PROC` is a child of root process # - `CURRENT_PROC` does not create any child processes - # + # # It looks for its brothers (and their children) p1 to pN in order # to compute real CPU usage. # - CURRENT_PROC @@ -74,6 +76,15 @@ def _get_brother_processes(self) -> list[psutil.Process]: children = self._get_children_processes(parent_pid) return [c for c in children if c.pid != current_process.pid] + def _get_cpu_usage(self, pid: int) -> float: + cmd = f"ps -p {pid} -o %cpu --no-headers" + output = subprocess.check_output(cmd, shell=True, universal_newlines=True) + try: + return float(output) + except ValueError: + print(f"Could not parse {pid} cpu usage: {output}") + return float(0) + def _get_total_cpu_usage(self) -> float: with ThreadPoolExecutor(max_workers=10) as executor: futures = [ @@ -122,6 +133,23 @@ async def run(self): activity_manager = ActivityManager(CHECK_INTERVAL_S) +class DebugHandler(tornado.web.RequestHandler): + def get(self): + self.write( + json.dumps( + { + "cpu_usage": { + "current": activity_manager.cpu_usage_monitor._get_total_cpu_usage(), + "busy": activity_manager.cpu_usage_monitor.are_children_busy(), + }, + "kernal_monitor": { + "busy": activity_manager.jupyter_kernel_monitor.are_kernels_busy() + }, + } + ) + ) + + class MainHandler(tornado.web.RequestHandler): def get(self): idle_seconds = activity_manager.get_idle_seconds() @@ -131,7 +159,12 @@ def get(self): def make_app() -> tornado.web.Application: - return tornado.web.Application([(r"/", MainHandler)]) + return tornado.web.Application( + [ + (r"/", MainHandler), + (r"/debug", DebugHandler), + ] + ) async def main():