From 2ef3879da60d7339b8010bc37aa59aa311e4bb05 Mon Sep 17 00:00:00 2001 From: Zachary Sailer Date: Mon, 9 May 2022 12:56:44 -0700 Subject: [PATCH] show last syncing in kernel syncing menu (#361) --- .../configurables/kernelspecs.py | 22 +++-- .../configurables/session_manager.py | 91 +++++-------------- .../schemas/sessions/syncing-state.v1.yaml | 21 ++++- setup.cfg | 2 +- src/kernelinfo.tsx | 2 +- src/runningtab.tsx | 32 ++++--- 6 files changed, 77 insertions(+), 93 deletions(-) diff --git a/data_studio_jupyter_extensions/configurables/kernelspecs.py b/data_studio_jupyter_extensions/configurables/kernelspecs.py index c1a5191205..530d3e3087 100644 --- a/data_studio_jupyter_extensions/configurables/kernelspecs.py +++ b/data_studio_jupyter_extensions/configurables/kernelspecs.py @@ -1,3 +1,4 @@ +import copy import os import time @@ -24,15 +25,18 @@ class DSKernelSpec(KernelSpec): @classmethod def from_dict(cls, kernelspec_dict): """Translate Notebook Service kernel spec responses into Jupyter kernelspecs""" - model = { - "name": kernelspec_dict["metadata"]["datastudio"][-1], - "argv": kernelspec_dict.get("argv", []), - "env": kernelspec_dict.get("env", {}), - "display_name": kernelspec_dict.get("display_name", ""), - "language": kernelspec_dict.get("language", ""), - "interrupt_mode": kernelspec_dict.get("interruptMode", "message"), - "metadata": kernelspec_dict.get("metadata", {}), - } + model = copy.deepcopy(kernelspec_dict) + model.update( + { + "name": kernelspec_dict["metadata"]["datastudio"][-1], + "argv": kernelspec_dict.get("argv", []), + "env": kernelspec_dict.get("env", {}), + "display_name": kernelspec_dict.get("display_name", ""), + "language": kernelspec_dict.get("language", ""), + "interrupt_mode": kernelspec_dict.get("interruptMode", "message"), + "metadata": kernelspec_dict.get("metadata", {}), + } + ) out = cls(**model) return out diff --git a/data_studio_jupyter_extensions/configurables/session_manager.py b/data_studio_jupyter_extensions/configurables/session_manager.py index b5b6b7e60c..8291d51603 100644 --- a/data_studio_jupyter_extensions/configurables/session_manager.py +++ b/data_studio_jupyter_extensions/configurables/session_manager.py @@ -1,3 +1,4 @@ +import datetime import os.path as osp import uuid from dataclasses import dataclass @@ -8,7 +9,6 @@ from jupyter_server.services.sessions.sessionmanager import SessionManager from jupyter_server_synchronizer import SynchronizerSessionManager from jupyter_server_synchronizer.kernel_records import KernelRecord -from jupyter_server_synchronizer.kernel_records import KernelRecordList from tornado.escape import json_decode from traitlets import default from traitlets import Instance @@ -34,6 +34,7 @@ class DataStudioSessionManager(SynchronizerSessionManager): database_filepath = Unicode(KERNEL_SESSION_DB_PATH) telemetry_bus = Instance(TelemetryBus) kernel_record_class = Type(RemoteKernelRecord) + _last_sync = Unicode("Unknown") # We need to define this as a static method because in the base class, # fetch_running_kernels is a configurable trait that takes a function @@ -98,87 +99,43 @@ async def list_sessions(self): self.telemetry_bus.record_event( schema_name="event.datastudio.jupyter.com/syncing-state", version=1, - event={"state": "Syncing..."}, + event={ + "syncing": True, + "msg": "Syncing running sessions...", + "last_sync": self._last_sync, + }, ) # Run the synchronizer loop try: await self.sync_managers() + # Update the last sync time + self._last_sync = datetime.datetime.now().strftime("%c") self.telemetry_bus.record_event( schema_name="event.datastudio.jupyter.com/syncing-state", version=1, - event={"state": "Synced."}, + event={ + "syncing": False, + "msg": "Successfully synced.", + "last_sync": self._last_sync, + }, ) except Exception as e: + last_sync = datetime.datetime.now().strftime("%c") self.telemetry_bus.record_event( schema_name="event.datastudio.jupyter.com/syncing-state", version=1, - event={"state": "Failed to sync. This tab might be out of date."}, + event={ + "syncing": False, + "msg": ( + "Failed to sync:" + f"{last_sync}" + "This tab might be out of date." + ), + "last_sync": self._last_sync, + }, ) self.log.error(e) pass out = await SessionManager.list_sessions(self) return out - - async def sync_kernels(self): - """Synchronize the kernel manager, kernel database, and - remote kernel service. - """ - self._kernel_records = KernelRecordList() - await self.fetch_kernel_records() - self.log.info("Kernel List at the start of the sync\n") - self.log.info(str(self._kernel_records)) - - self.remove_stale_kernels() - self.log.info("Stale Kernels removed\n") - self.log.info(str(self._kernel_records)) - await self.hydrate_kernel_managers() - self.record_kernels() - - def record_kernels(self): - """Record the current kernels to the kernel database.""" - for kernel in self._kernel_records._records: - conditions = [ - # Kernel isn't already recorded - not kernel.recorded, - # Kernel has all identifiers - all(kernel.get_identifier_values()), - # Kernel is still running - kernel.alive, - ] - if all(conditions): - try: - self.kernel_table.save(kernel) - kernel.recorded = True - except Exception as e: - self.log.error(f"Could not record kernel. {kernel}") - self.log.error(e) - - def remove_stale_kernels(self): - """Remove kernels from the database that are no longer running.""" - for k in self._kernel_records._records: - if not k.alive: - try: - self._kernel_records.remove(k) - if k.recorded: - self.kernel_table.delete(kernel_id=k.kernel_id) - except Exception as e: - self.log.error(f"Could not remove kernel from records: {k}") - self.log.error(e) - - async def hydrate_kernel_managers(self): - """Create KernelManagers for kernels found for this - server but are not yet managed. - """ - for k in self._kernel_records._records: - if not k.managed and k.alive: - if not k.kernel_id: - kernel_id = str(uuid.uuid4()) - k.kernel_id = kernel_id - kwargs = k.get_active_fields() - try: - await self.kernel_manager.start_kernel(**kwargs) - k.managed = True - except Exception as e: - self.log.error(f"Could not hydrate a manager for kernel: {k}") - self.log.error(e) diff --git a/data_studio_jupyter_extensions/extensions/telemetry/schemas/sessions/syncing-state.v1.yaml b/data_studio_jupyter_extensions/extensions/telemetry/schemas/sessions/syncing-state.v1.yaml index c01ade4752..482450db36 100644 --- a/data_studio_jupyter_extensions/extensions/telemetry/schemas/sessions/syncing-state.v1.yaml +++ b/data_studio_jupyter_extensions/extensions/telemetry/schemas/sessions/syncing-state.v1.yaml @@ -5,9 +5,22 @@ description: | Emit a message from the kernel provisioner type: object properties: - state: - title: Syncing State + syncing: + title: Syncing + type: boolean description: | - State of the synchronizer + Is the session list currently syncing? + msg: + title: Message + type: string + description: | + The message to emit when the synchronizer is running. + last_sync: + title: Last sync time + type: string + description: | + The last time the server synced successfully. required: - - state + - syncing + - msg + - last_sync diff --git a/setup.cfg b/setup.cfg index 478d0bfdb5..d9d643f3ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ install_requires = jupyter_client~=7.3.0 validators~=0.18.2 pycryptodome~=3.14.1 - jupyter_server_synchronizer==0.0.6 + jupyter_server_synchronizer==0.0.7 [options.entry_points] jupyter_client.kernel_provisioners = diff --git a/src/kernelinfo.tsx b/src/kernelinfo.tsx index cb7bac3efd..fd9b8eb6a7 100644 --- a/src/kernelinfo.tsx +++ b/src/kernelinfo.tsx @@ -49,7 +49,7 @@ const KernelSessionInfo = (props: IKernelSessionInfo) => {
Kernel session info
- + ); return content; diff --git a/src/runningtab.tsx b/src/runningtab.tsx index b2014eebd0..74facdfe69 100644 --- a/src/runningtab.tsx +++ b/src/runningtab.tsx @@ -20,7 +20,7 @@ import { addKernelRunningSessionManager } from '@jupyterlab/running-extension/li import { addOpenTabsSessionManager } from '@jupyterlab/running-extension/lib/opentabs'; import { TelemetryListener } from './telemetrylistener'; -import { InlineSpinner } from '@tidbits/react-tidbits'; +import { InlineSpinner, Text } from '@tidbits/react-tidbits'; import { ThemeProvider } from 'styled-components'; import theme from '@tidbits/react-tidbits/theme'; @@ -35,20 +35,26 @@ namespace CommandIDs { * The status information for the kernel from notebook service */ export interface IStatus { - status: string; + syncing: boolean; + msg: string; + last_sync: string; } export class SyncingRunningSessions extends RunningSessions { - private _syncing_status: string; + private _status: IStatus; setState(status: IStatus) { - this._syncing_status = status.status; + this._status = status; this.update(); } constructor(managers: IRunningSessionManagers, translator?: ITranslator) { super(managers, translator); - this._syncing_status = 'This tab might need to be refreshed.'; + this._status = { + syncing: false, + msg: 'This tab might need to be refreshed.', + last_sync: 'Unknown' + }; const runningSessions = this; // Create a listener for kernel-blocked events. const listener = TelemetryListener.getInstance(); @@ -56,7 +62,7 @@ export class SyncingRunningSessions extends RunningSessions { listener.addCallback( 'event.datastudio.jupyter.com/syncing-state', function (data: any) { - runningSessions.setState({ status: data.state }); + runningSessions.setState(data); } ); } @@ -67,12 +73,16 @@ export class SyncingRunningSessions extends RunningSessions { {super.render()}
- + + + {this._status.msg} + + Last successful sync: + {this._status.last_sync} - {this._syncing_status}
);