Jupyterhub-singleuser consuming significant and constant CPU

I’m using python 3.11 + jupyterhub 4.0.2 + jupyterlab 4.0.5 and am currently having a problem with jupyterhub-singleuser consuming constant and high CPU even when the user is not connected to the web console. I am launching it like so via jupyterhub_config.py

c.Spawner.cmd = ['bash', '-l', '-c', 'exec jupyterhub-singleuser "$@"', '--']

My reproducing steps are:

  1. Log into jupyterhub via web browser
  2. Exit jupyterhub/lab by going to File → Logout
  3. Observe on the server that jupyterhub-singleuser is running and chewing lots of CPU
akrherz  3566392 20.4  0.0 5029420 241284 ?      Ssl  10:35   1:54 /opt/miniconda3/envs/prod/bin/python3.11 /opt/miniconda3/envs/prod/bin/jupyterhub-singleuser --NotebookApp.allow_origin=jupyterhub.fqdn.redacted

Any ideas?

CPU usage never goes down? Are you testing this locally on your workstation using LocalProcessSpawner? Do you see anything strange in logs? Could you share them?

1 Like

Correct, the above now looks like this without me having any interaction with the server

akrherz  3566392 11.4  0.0 5029420 244008 ?      Ssl  10:35  16:58 /opt/miniconda3/envs/prod/bin/python3.11 /opt/miniconda3/envs/prod/bin/jupyterhub-singleuser --NotebookApp.allow_origin=...

I have not tried such a thing, this is happening on the server that my colleagues use.

Nothing logged since I logged out of the session.

How is your JupyterHub deployed? Even if you have logged out of JupyterLab, the backend server keeps running until you terminate the single user server. I was talking about the logs of that single user server.

Thank you for the response. I don’t know where the logs for the single user server are located? I only see the logs for the running of jupyterhub -f jupyterhub_config.py. Here’s my config

from oauthenticator.generic import GenericOAuthenticator

origin = 'jupyterhub...'
c.Spawner.args = [f'--NotebookApp.allow_origin={origin}']
c.JupyterHub.tornado_settings = {
    'headers': {
        'Access-Control-Allow-Origin': origin,
    },
}
c.Spawner.default_url = "/user/{username}/lab"
c.SingleUserNotebookApp.default_url = "/user/{username}/lab"
c.Spawner.ip = '127.0.0.1'
c.Spawner.cmd = ['bash', '-l', '-c', 'exec jupyterhub-singleuser "$@"', '--']

c.JupyterHub.authenticator_class = GenericOAuthenticator

c.GenericOAuthenticator.client_id = ...
c.GenericOAuthenticator.client_secret = ...

c.GenericOAuthenticator.allow_all = True
c.GenericOAuthenticator.oauth_callback_url = 'https://.../hub/oauth_callback'
c.GenericOAuthenticator.token_url = 'https://iastate.../oauth2/v1/token'
c.GenericOAuthenticator.userdata_url = 'https://iastate.../oauth2/v1/userinfo'
c.GenericOAuthenticator.username_key = "email"
c.GenericOAuthenticator.scope = ['openid', 'profile', 'email', 'address', 'phone', 'offline_access']
c.GenericOAuthenticator.login_service = "Okta@ISU"

c.NotebookApp.disable_check_xsrf = True

In that case your single user server logs will be logged along with JupyterHub logs. In any case, if you dont see anything strange in the logs, I assume it must be down to either polling or PeriodicCallbacks that backend jupyter_server uses. I am not quite sure though but this is my guess!

1 Like

hooking up py-spy in the user environment may also help identify the culprit.

1 Like

Thank you, I did not know about this fancy tool. Here’s the top output over about 1 minute after I exit jupyterhub

NotebookApp.allow_origin=jupyterhub....edu' (python v3.11.5)
Total Samples 6578
GIL: 8.00%, Active: 45.00%, Threads: 4

  %Own   %Total  OwnTime  TotalTime  Function (filename)                                                                
 25.00%  45.00%   13.84s    25.60s   ppid_map (psutil/_pslinux.py)
 18.00%  18.00%   10.44s    10.44s   open_binary (psutil/_common.py)
  1.00%   2.00%    1.17s     1.35s   pids (psutil/_pslinux.py)
  1.00%   1.00%   0.180s    0.180s   <listcomp> (psutil/_pslinux.py)
  0.00%  45.00%   0.100s    25.70s   children (psutil/__init__.py)
  0.00%   0.00%   0.030s    0.040s   virtual_memory (psutil/_pslinux.py)
  0.00%  45.00%   0.030s    25.85s   get_metric_values (jupyter_resource_usage/metrics.py)
  0.00%   0.00%   0.020s    0.020s   _worker (concurrent/futures/thread.py)
  0.00%   0.00%   0.020s    0.020s   set (prometheus_client/values.py)
  0.00%   0.00%   0.020s    0.040s   memory_info (psutil/_pslinux.py)
  0.00%  45.00%   0.020s    25.72s   wrapper (psutil/__init__.py)
  0.00%  45.00%   0.010s    25.86s   metrics (jupyter_resource_usage/metrics.py)
  0.00%   0.00%   0.010s    0.010s   _remove (_weakrefset.py)
  0.00%   0.00%   0.010s    0.020s   _schedule_next (tornado/ioloop.py)
  0.00%   0.00%   0.010s    0.050s   system_metric (jupyter_resource_usage/metrics.py)
  0.00%   0.00%   0.010s    0.010s   __init__ (asyncio/events.py)
  0.00%   0.00%   0.010s    0.010s   usage_percent (psutil/_common.py)
  0.00%  45.00%   0.010s    25.93s   _run_once (asyncio/base_events.py)
  0.00%   0.00%   0.010s    0.010s   _call_soon (asyncio/base_events.py)
  0.00%   0.00%   0.000s    0.010s   _init (psutil/__init__.py)
  0.00%   0.00%   0.000s    0.010s   add_timeout (tornado/ioloop.py)
  0.00%   0.00%   0.000s    0.040s   <listcomp> (jupyter_resource_usage/metrics.py)
  0.00%   0.00%   0.000s    0.020s   _bootstrap_inner (threading.py)
  0.00%   0.00%   0.000s    0.010s   __init__ (psutil/__init__.py)

It looks like jupyter_resource_usage could be the culprit. Try uninstalling it from your singleuser environment and see if that significantly reduces your CPU usage?

Oh my, thank you. I found jupyter-resource-usage=1.0.1 installed via conda-forge. I removed it and now jupyterhub-singleuser appears to be completely idle.

If you figure out the root cause for why jupyter-resource-usage uses so much CPU you could open an issue: Issues · jupyter-server/jupyter-resource-usage · GitHub

If I am right, that must be Prometheus metrics endpoint that is consuming the CPU. @akrherz Could you try with --ResourceUseDisplay.enable_prometheus_metrics=False in c.Spawner.args?

1 Like

That seems to be working as well. I reinstalled the extension, set your suggested flag to False and observed no CPU usage. Setting it back to True caused the CPU usage to return.

1 Like

@jtp What is the purpose of PrometheusHandle in jupyter-resource-usage? It is not very clear from me from the current code base.

Do you think it makes sense to set the default for enable_prometheus_metrics to False?

Not entirely sure, but it’s probably there so the metrics can be collected and displayed somewhere else alongside other metrics, for example in a Grafana dashboard.

I guess? Maybe worth discussing in an issue?

Not entirely sure, but it’s probably there so the metrics can be collected and displayed somewhere else alongside other metrics, for example in a Grafana dashboard.

That is what I thought but I do not see this handler registered with any endpoint.

Fair enough! I will open an issue.