Skip to content

Commit

Permalink
show last syncing in kernel syncing menu (jupyter-server#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zsailer authored and GitHub Enterprise committed May 9, 2022
1 parent b8fbf73 commit 2ef3879
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 93 deletions.
22 changes: 13 additions & 9 deletions data_studio_jupyter_extensions/configurables/kernelspecs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import os
import time

Expand All @@ -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

Expand Down
91 changes: 24 additions & 67 deletions data_studio_jupyter_extensions/configurables/session_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import os.path as osp
import uuid
from dataclasses import dataclass
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
2 changes: 1 addition & 1 deletion src/kernelinfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const KernelSessionInfo = (props: IKernelSessionInfo) => {
<div id="title">Kernel session info</div>
<KernelInfoListItem label={'Session ID'} value={props.session_id} />
<KernelInfoListItem label="Kernel ID" value={props.kernel_id} />
<KernelInfoListItem label="Pod ID" value={props.process_id} />
<KernelInfoListItem label="Remote Kernel ID" value={props.process_id} />
</div>
);
return content;
Expand Down
32 changes: 21 additions & 11 deletions src/runningtab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -35,28 +35,34 @@ 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();

listener.addCallback(
'event.datastudio.jupyter.com/syncing-state',
function (data: any) {
runningSessions.setState({ status: data.state });
runningSessions.setState(data);
}
);
}
Expand All @@ -67,12 +73,16 @@ export class SyncingRunningSessions extends RunningSessions {
{super.render()}
<div id="status">
<ThemeProvider theme={theme}>
<InlineSpinner
mr="spacer10"
visible={this._syncing_status == 'Syncing...'}
/>
<Text sa="spacer10" sb="spacer40">
<InlineSpinner
mr="spacer10"
visible={this._status.syncing == true}
/>
{this._status.msg}
</Text>
<Text>Last successful sync:</Text>
<Text>{this._status.last_sync}</Text>
</ThemeProvider>
{this._syncing_status}
</div>
</>
);
Expand Down

0 comments on commit 2ef3879

Please sign in to comment.