1
1
import os
2
- import json
3
- import psutil
4
2
from traitlets import Bool , Float , Int , Union , default
5
3
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
4
+ from tornado import ioloop
5
+ from nbresuse .prometheus import PrometheusHandler
9
6
10
7
try :
11
8
# Traitlets >= 4.3.3
12
9
from traitlets import Callable
13
10
except ImportError :
14
11
from .utils import Callable
15
12
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
13
99
14
def _jupyter_server_extension_paths ():
100
15
"""
@@ -104,6 +19,7 @@ def _jupyter_server_extension_paths():
104
19
'module' : 'nbresuse' ,
105
20
}]
106
21
22
+
107
23
def _jupyter_nbextension_paths ():
108
24
"""
109
25
Set up the notebook extension for displaying metrics
@@ -115,6 +31,7 @@ def _jupyter_nbextension_paths():
115
31
"require" : "nbresuse/main"
116
32
}]
117
33
34
+
118
35
class ResourceUseDisplay (Configurable ):
119
36
"""
120
37
Holds server-side configuration for nbresuse
@@ -151,7 +68,7 @@ def _mem_limit_default(self):
151
68
return int (os .environ .get ('MEM_LIMIT' , 0 ))
152
69
153
70
track_cpu_percent = Bool (
154
- default_value = False ,
71
+ default_value = True ,
155
72
help = """
156
73
Set to True in order to enable reporting of CPU usage statistics.
157
74
"""
@@ -186,11 +103,12 @@ def _mem_limit_default(self):
186
103
def _cpu_limit_default (self ):
187
104
return float (os .environ .get ('CPU_LIMIT' , 0 ))
188
105
106
+
189
107
def load_jupyter_server_extension (nbapp ):
190
108
"""
191
109
Called during notebook start
192
110
"""
193
111
resuseconfig = ResourceUseDisplay (parent = nbapp )
194
112
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 )] )
113
+ callback = ioloop . PeriodicCallback ( PrometheusHandler ( nbapp ), 1000 )
114
+ callback . start ( )
0 commit comments