diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py index e821a6f2..5c15d93a 100644 --- a/cylc/uiserver/app.py +++ b/cylc/uiserver/app.py @@ -65,6 +65,7 @@ from tornado import ioloop from tornado.web import RedirectHandler from traitlets import ( + Bool, Dict, Float, Int, @@ -75,12 +76,14 @@ default, validate, ) +from types import SimpleNamespace from jupyter_server.extension.application import ExtensionApp from cylc.flow.network.graphql import ( CylcGraphQLBackend, IgnoreFieldMiddleware ) +from cylc.flow.profiler import Profiler from cylc.uiserver import ( __file__ as uis_pkg, ) @@ -324,6 +327,16 @@ class CylcUIServer(ExtensionApp): ''', default_value=1 ) + profile = Bool( + config=True, + help=''' + Turn on Python profiling. + + The profile results will be saved to ~/.cylc/uiserver/profile.prof + in cprofile format. + ''', + default_value=False, + ) @validate('ui_build_dir') def _check_ui_build_dir_exists(self, proposed): @@ -403,6 +416,8 @@ def initialize_settings(self): self.settings.update({'':...}) """ super().initialize_settings() + + # startup messages self.log.info("Starting Cylc UI Server") self.log.info(f'Serving UI from: {self.ui_path}') self.log.debug( @@ -411,6 +426,16 @@ def initialize_settings(self): for key, value in self.config['CylcUIServer'].items() ) ) + + # start profiling + self.profiler = Profiler( + # the profiler is designed to attach to a Cylc scheduler + schd=SimpleNamespace(workflow_log_dir=USER_CONF_ROOT), + # profiling is turned on via the "profile" traitlet + enabled=self.profile, + ) + self.profiler.start() + # start the async scan task running (do this on server start not init) ioloop.IOLoop.current().add_callback( self.workflows_mgr.run @@ -540,3 +565,4 @@ async def stop_extension(self): self.data_store_mgr.executor.shutdown(wait=False) # Destroy ZeroMQ context of all sockets self.workflows_mgr.context.destroy() + self.profiler.stop()