1
1
import os
2
- import json
3
- import psutil
2
+
3
+ from tornado import ioloop
4
4
from traitlets import Bool , Float , Int , Union , default
5
5
from traitlets .config import Configurable
6
- from notebook .utils import url_path_join
7
- from notebook .base .handlers import IPythonHandler
8
- from tornado import web
6
+
7
+ from nbresuse .prometheus import PrometheusHandler
9
8
10
9
try :
11
10
# Traitlets >= 4.3.3
12
11
from traitlets import Callable
13
12
except ImportError :
14
13
from .utils import Callable
15
14
16
- from concurrent .futures import ThreadPoolExecutor
17
- from tornado .concurrent import run_on_executor
18
-
19
- class MetricsHandler (IPythonHandler ):
20
- def initialize (self ):
21
- super ().initialize ()
22
- self .cpu_percent = 0
23
-
24
- # https://www.tornadoweb.org/en/stable/concurrent.html#tornado.concurrent.run_on_executor
25
- self .executor = ThreadPoolExecutor (max_workers = 10 )
26
-
27
- self .cpu_count = psutil .cpu_count ()
28
-
29
- @run_on_executor
30
- def update_cpu_percent (self , all_processes ):
31
-
32
- def get_cpu_percent (p ):
33
- try :
34
- return p .cpu_percent (interval = 0.05 )
35
- # Avoid littering logs with stack traces complaining
36
- # about dead processes having no CPU usage
37
- except :
38
- return 0
39
-
40
- return sum ([get_cpu_percent (p ) for p in all_processes ])
41
-
42
- @web .authenticated
43
- async def get (self ):
44
- """
45
- Calculate and return current resource usage metrics
46
- """
47
- config = self .settings ['nbresuse_display_config' ]
48
- cur_process = psutil .Process ()
49
- all_processes = [cur_process ] + cur_process .children (recursive = True )
50
- limits = {}
51
-
52
- # Get memory information
53
- rss = sum ([p .memory_info ().rss for p in all_processes ])
54
-
55
- if callable (config .mem_limit ):
56
- mem_limit = config .mem_limit (rss = rss )
57
- else : # mem_limit is an Int
58
- mem_limit = config .mem_limit
59
-
60
- # A better approach would use cpu_affinity to account for the
61
- # fact that the number of logical CPUs in the system is not
62
- # necessarily the same as the number of CPUs the process
63
- # can actually use. But cpu_affinity isn't available for OS X.
64
- cpu_count = psutil .cpu_count ()
65
-
66
- if config .track_cpu_percent :
67
- self .cpu_percent = await self .update_cpu_percent (all_processes )
68
-
69
- if config .mem_limit != 0 :
70
- limits ['memory' ] = {
71
- 'rss' : mem_limit
72
- }
73
- if config .mem_warning_threshold != 0 :
74
- limits ['memory' ]['warn' ] = (mem_limit - rss ) < (mem_limit * config .mem_warning_threshold )
75
-
76
- # Optionally get CPU information
77
- if config .track_cpu_percent :
78
- self .cpu_percent = await self .update_cpu_percent (all_processes )
79
-
80
- if config .cpu_limit != 0 :
81
- limits ['cpu' ] = {
82
- 'cpu' : config .cpu_limit
83
- }
84
- if config .cpu_warning_threshold != 0 :
85
- limits ['cpu' ]['warn' ] = (config .cpu_limit - self .cpu_percent ) < (config .cpu_limit * config .cpu_warning_threshold )
86
-
87
- metrics = {
88
- 'rss' : rss ,
89
- 'limits' : limits ,
90
- }
91
- if config .track_cpu_percent :
92
- metrics .update (cpu_percent = self .cpu_percent ,
93
- cpu_count = self .cpu_count )
94
-
95
- self .log .debug ("NBResuse metrics: %s" , metrics )
96
- self .write (json .dumps (metrics ))
97
-
98
15
99
16
def _jupyter_server_extension_paths ():
100
17
"""
@@ -104,6 +21,7 @@ def _jupyter_server_extension_paths():
104
21
'module' : 'nbresuse' ,
105
22
}]
106
23
24
+
107
25
def _jupyter_nbextension_paths ():
108
26
"""
109
27
Set up the notebook extension for displaying metrics
@@ -115,6 +33,7 @@ def _jupyter_nbextension_paths():
115
33
"require" : "nbresuse/main"
116
34
}]
117
35
36
+
118
37
class ResourceUseDisplay (Configurable ):
119
38
"""
120
39
Holds server-side configuration for nbresuse
@@ -142,7 +61,7 @@ class ResourceUseDisplay(Configurable):
142
61
Note that this does not actually limit the user's memory usage!
143
62
144
63
Defaults to reading from the `MEM_LIMIT` environment variable. If
145
- set to 0, no memory limit is displayed.
64
+ set to 0, the max memory available is displayed.
146
65
"""
147
66
).tag (config = True )
148
67
@@ -178,19 +97,20 @@ def _mem_limit_default(self):
178
97
Note that this does not actually limit the user's CPU usage!
179
98
180
99
Defaults to reading from the `CPU_LIMIT` environment variable. If
181
- set to 0, no CPU usage limit is displayed.
100
+ set to 0, the total CPU count available is displayed.
182
101
"""
183
102
).tag (config = True )
184
103
185
104
@default ('cpu_limit' )
186
105
def _cpu_limit_default (self ):
187
106
return float (os .environ .get ('CPU_LIMIT' , 0 ))
188
107
108
+
189
109
def load_jupyter_server_extension (nbapp ):
190
110
"""
191
111
Called during notebook start
192
112
"""
193
113
resuseconfig = ResourceUseDisplay (parent = nbapp )
194
114
nbapp .web_app .settings ['nbresuse_display_config' ] = resuseconfig
195
- route_pattern = url_path_join ( nbapp . web_app . settings [ 'base_url' ], '/metrics' )
196
- nbapp . web_app . add_handlers ( '.*' , [( route_pattern , MetricsHandler )] )
115
+ callback = ioloop . PeriodicCallback ( PrometheusHandler ( nbapp ), 1000 )
116
+ callback . start ( )
0 commit comments