15
15
from enum import Enum
16
16
17
17
import zmq
18
+ from jupyter_events import EventLogger # type: ignore[import]
18
19
from traitlets import Any
19
20
from traitlets import Bool
20
21
from traitlets import default
33
34
from .provisioning import KernelProvisionerFactory as KPF
34
35
from .utils import ensure_async
35
36
from .utils import run_sync
37
+ from jupyter_client import DEFAULT_EVENTS_SCHEMA_PATH
38
+ from jupyter_client import JUPYTER_CLIENT_EVENTS_URI
36
39
from jupyter_client import KernelClient
37
40
from jupyter_client import kernelspec
38
41
@@ -91,6 +94,27 @@ class KernelManager(ConnectionFileMixin):
91
94
This version starts kernels with Popen.
92
95
"""
93
96
97
+ event_schema_id = JUPYTER_CLIENT_EVENTS_URI + "/kernel_manager/v1"
98
+ event_logger = Instance (EventLogger ).tag (config = True )
99
+
100
+ @default ("event_logger" )
101
+ def _default_event_logger (self ):
102
+ if self .parent and hasattr (self .parent , "event_logger" ):
103
+ return self .parent .event_logger
104
+ else :
105
+ # If parent does not have an event logger, create one.
106
+ logger = EventLogger ()
107
+ schema_path = DEFAULT_EVENTS_SCHEMA_PATH / "kernel_manager" / "v1.yaml"
108
+ logger .register_event_schema (schema_path )
109
+ return logger
110
+
111
+ def _emit (self , * , action : str ) -> None :
112
+ """Emit event using the core event schema from Jupyter Server's Contents Manager."""
113
+ self .event_logger .emit (
114
+ schema_id = self .event_schema_id ,
115
+ data = {"action" : action , "kernel_id" : self .kernel_id , "caller" : "kernel_manager" },
116
+ )
117
+
94
118
_ready : t .Union [Future , CFuture ]
95
119
96
120
def __init__ (self , * args , ** kwargs ):
@@ -308,6 +332,7 @@ async def _async_launch_kernel(self, kernel_cmd: t.List[str], **kw: t.Any) -> No
308
332
assert self .provisioner .has_process
309
333
# Provisioner provides the connection information. Load into kernel manager and write file.
310
334
self ._force_connection_info (connection_info )
335
+ self ._emit (action = "launch" )
311
336
312
337
_launch_kernel = run_sync (_async_launch_kernel )
313
338
@@ -350,6 +375,7 @@ async def _async_pre_start_kernel(
350
375
)
351
376
kw = await self .provisioner .pre_launch (** kw )
352
377
kernel_cmd = kw .pop ('cmd' )
378
+ self ._emit (action = "pre_start" )
353
379
return kernel_cmd , kw
354
380
355
381
pre_start_kernel = run_sync (_async_pre_start_kernel )
@@ -366,6 +392,7 @@ async def _async_post_start_kernel(self, **kw: t.Any) -> None:
366
392
self ._connect_control_socket ()
367
393
assert self .provisioner is not None
368
394
await self .provisioner .post_launch (** kw )
395
+ self ._emit (action = "post_start" )
369
396
370
397
post_start_kernel = run_sync (_async_post_start_kernel )
371
398
@@ -401,6 +428,7 @@ async def _async_request_shutdown(self, restart: bool = False) -> None:
401
428
assert self .provisioner is not None
402
429
await self .provisioner .shutdown_requested (restart = restart )
403
430
self ._shutdown_status = _ShutdownStatus .ShutdownRequest
431
+ self ._emit (action = "request_shutdown" )
404
432
405
433
request_shutdown = run_sync (_async_request_shutdown )
406
434
@@ -442,6 +470,7 @@ async def _async_finish_shutdown(
442
470
if self .has_kernel :
443
471
assert self .provisioner is not None
444
472
await self .provisioner .wait ()
473
+ self ._emit (action = "finish_shutdown" )
445
474
446
475
finish_shutdown = run_sync (_async_finish_shutdown )
447
476
@@ -459,6 +488,7 @@ async def _async_cleanup_resources(self, restart: bool = False) -> None:
459
488
460
489
if self .provisioner :
461
490
await self .provisioner .cleanup (restart = restart )
491
+ self ._emit (action = "cleanup_resources" )
462
492
463
493
cleanup_resources = run_sync (_async_cleanup_resources )
464
494
@@ -481,6 +511,7 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False)
481
511
Will this kernel be restarted after it is shutdown. When this
482
512
is True, connection files will not be cleaned up.
483
513
"""
514
+ self ._emit (action = "shutdown_started" )
484
515
self .shutting_down = True # Used by restarter to prevent race condition
485
516
# Stop monitoring for restarting while we shutdown.
486
517
self .stop_restarter ()
@@ -498,6 +529,7 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False)
498
529
await ensure_async (self .finish_shutdown (restart = restart ))
499
530
500
531
await ensure_async (self .cleanup_resources (restart = restart ))
532
+ self ._emit (action = "shutdown_finished" )
501
533
502
534
shutdown_kernel = run_sync (_async_shutdown_kernel )
503
535
@@ -528,6 +560,7 @@ async def _async_restart_kernel(
528
560
Any options specified here will overwrite those used to launch the
529
561
kernel.
530
562
"""
563
+ self ._emit (action = "restart_started" )
531
564
if self ._launch_args is None :
532
565
raise RuntimeError ("Cannot restart the kernel. No previous call to 'start_kernel'." )
533
566
@@ -540,6 +573,7 @@ async def _async_restart_kernel(
540
573
# Start new kernel.
541
574
self ._launch_args .update (kw )
542
575
await ensure_async (self .start_kernel (** self ._launch_args ))
576
+ self ._emit (action = "restart_finished" )
543
577
544
578
restart_kernel = run_sync (_async_restart_kernel )
545
579
@@ -576,6 +610,7 @@ async def _async_kill_kernel(self, restart: bool = False) -> None:
576
610
# Process is no longer alive, wait and clear
577
611
if self .has_kernel :
578
612
await self .provisioner .wait ()
613
+ self ._emit (action = "kill" )
579
614
580
615
_kill_kernel = run_sync (_async_kill_kernel )
581
616
@@ -597,6 +632,7 @@ async def _async_interrupt_kernel(self) -> None:
597
632
self .session .send (self ._control_socket , msg )
598
633
else :
599
634
raise RuntimeError ("Cannot interrupt kernel. No kernel is running!" )
635
+ self ._emit (action = "interrupt" )
600
636
601
637
interrupt_kernel = run_sync (_async_interrupt_kernel )
602
638
0 commit comments