Skip to content

Commit 63500d8

Browse files
authored
Merge pull request #24 from mwakaba2/fix-overwritten-start-times
Track cell start and end times in a metadata object
2 parents 00ff962 + 4569690 commit 63500d8

File tree

6 files changed

+4083
-3740
lines changed

6 files changed

+4083
-3740
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@
4747
"dependencies": {
4848
"@jupyterlab/application": "^3.0.6",
4949
"@jupyterlab/notebook": "^3.1.0-beta.1",
50-
"@jupyterlab/settingregistry": "^3.0.3"
50+
"@jupyterlab/settingregistry": "^3.0.3",
51+
"@types/lru-cache": "^5.1.1",
52+
"lru-cache": "^6.0.0",
53+
"moment": "^2.29.1"
5154
},
5255
"devDependencies": {
5356
"@jupyterlab/builder": "^3.0.0",

src/index.ts

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ import { KernelError, Notebook, NotebookActions } from '@jupyterlab/notebook';
66
import { Cell } from '@jupyterlab/cells';
77
import { ISettingRegistry } from '@jupyterlab/settingregistry';
88
import { ICodeCellModel } from '@jupyterlab/cells';
9-
9+
import LRU from 'lru-cache';
10+
import moment from 'moment';
1011
import { checkBrowserNotificationSettings } from './settings';
1112

13+
interface ICellExecutionMetadata {
14+
index: number;
15+
scheduledTime: Date;
16+
endTime?: Date;
17+
startTime?: Date;
18+
}
19+
1220
/**
1321
* Constructs notification message and displays it.
1422
*/
@@ -50,38 +58,36 @@ function displayNotification(
5058
function triggerNotification(
5159
cell: Cell,
5260
notebook: Notebook,
53-
cellStartTime: Date,
54-
cellEndTime: Date,
61+
executionMetadata: ICellExecutionMetadata,
5562
minimumCellExecutionTime: number,
5663
reportCellNumber: boolean,
5764
reportCellExecutionTime: boolean,
5865
cellNumberType: string,
5966
failedExecution: boolean,
6067
error: KernelError | null
6168
) {
62-
const codeCell = cell.model.type === 'code';
63-
const nonEmptyCell = cell.model.value.text.length > 0;
64-
if (codeCell && nonEmptyCell) {
65-
const codeCellModel = cell.model as ICodeCellModel;
66-
const diff = new Date(<any>cellEndTime - <any>cellStartTime);
67-
const diffSeconds = Math.floor(diff.getTime() / 1000);
68-
if (diffSeconds >= minimumCellExecutionTime) {
69-
const cellDuration = diff.toISOString().substr(11, 8);
70-
const cellNumber =
71-
cellNumberType === 'cell_index'
72-
? notebook.activeCellIndex
73-
: codeCellModel.executionCount;
74-
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
75-
displayNotification(
76-
cellDuration,
77-
cellNumber,
78-
notebookName,
79-
reportCellNumber,
80-
reportCellExecutionTime,
81-
failedExecution,
82-
error
83-
);
84-
}
69+
const { startTime, endTime, index: cellIndex } = executionMetadata;
70+
const codeCellModel = cell.model as ICodeCellModel;
71+
const cellDuration = moment
72+
.utc(moment(endTime).diff(startTime))
73+
.format('HH:mm:ss');
74+
const diffSeconds = moment.duration(cellDuration).asSeconds();
75+
console.log(cellDuration, diffSeconds);
76+
if (diffSeconds >= minimumCellExecutionTime) {
77+
const cellNumber =
78+
cellNumberType === 'cell_index'
79+
? cellIndex
80+
: codeCellModel.executionCount;
81+
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
82+
displayNotification(
83+
cellDuration,
84+
cellNumber,
85+
notebookName,
86+
reportCellNumber,
87+
reportCellExecutionTime,
88+
failedExecution,
89+
error
90+
);
8591
}
8692
}
8793

@@ -96,6 +102,15 @@ const extension: JupyterFrontEndPlugin<void> = {
96102
let reportCellExecutionTime = true;
97103
let reportCellNumber = true;
98104
let cellNumberType = 'cell_index';
105+
const cellExecutionMetadataTable: LRU<
106+
string,
107+
ICellExecutionMetadata
108+
> = new LRU({
109+
max: 500 * 5 // to save 500 notebooks x 5 cells
110+
});
111+
const recentNotebookExecutionTimes: LRU<string, Date> = new LRU({
112+
max: 500
113+
});
99114

100115
if (settingRegistry) {
101116
const setting = await settingRegistry.load(extension.id);
@@ -113,30 +128,53 @@ const extension: JupyterFrontEndPlugin<void> = {
113128
setting.changed.connect(updateSettings);
114129
}
115130

116-
let cellStartTime = new Date();
117-
118131
NotebookActions.executionScheduled.connect((_, args) => {
132+
const { cell, notebook } = args;
119133
if (enabled) {
120-
cellStartTime = new Date();
134+
cellExecutionMetadataTable.set(cell.model.id, {
135+
index: notebook.activeCellIndex,
136+
scheduledTime: new Date()
137+
});
121138
}
122139
});
123140

124141
NotebookActions.executed.connect((_, args) => {
125142
if (enabled) {
126-
const { cell, notebook, success, error } = args;
127143
const cellEndTime = new Date();
128-
triggerNotification(
129-
cell,
130-
notebook,
131-
cellStartTime,
132-
cellEndTime,
133-
minimumCellExecutionTime,
134-
reportCellNumber,
135-
reportCellExecutionTime,
136-
cellNumberType,
137-
!success,
138-
error
139-
);
144+
const { cell, notebook, success, error } = args;
145+
const codeCell = cell.model.type === 'code';
146+
const nonEmptyCell = cell.model.value.text.length > 0;
147+
if (codeCell && nonEmptyCell) {
148+
const cellId = cell.model.id;
149+
const notebookId = notebook.id;
150+
const cellExecutionMetadata = cellExecutionMetadataTable.get(cellId);
151+
const scheduledTime = cellExecutionMetadata.scheduledTime;
152+
// Get the cell's execution scheduled time if the recent notebook execution state doesn't exist.
153+
// This happens commonly for first time notebook executions or notebooks that haven't been executed for a while.
154+
const recentExecutedCellTime =
155+
recentNotebookExecutionTimes.get(notebookId) || scheduledTime;
156+
157+
// Multiple cells can be scheduled at the same time, and the schedule time doesn't necessarily equate to the actual start time.
158+
// If another cell has been executed more recently than the current cell's scheduled time, treat the recent execution as the cell's start time.
159+
cellExecutionMetadata.startTime =
160+
scheduledTime >= recentExecutedCellTime
161+
? scheduledTime
162+
: recentExecutedCellTime;
163+
cellExecutionMetadata.endTime = cellEndTime;
164+
recentNotebookExecutionTimes.set(notebookId, cellEndTime);
165+
166+
triggerNotification(
167+
cell,
168+
notebook,
169+
cellExecutionMetadata,
170+
minimumCellExecutionTime,
171+
reportCellNumber,
172+
reportCellExecutionTime,
173+
cellNumberType,
174+
!success,
175+
error
176+
);
177+
}
140178
}
141179
});
142180
}

tutorial/py3_demo.ipynb

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
"cells": [
33
{
44
"cell_type": "code",
5-
"execution_count": 1,
5+
"execution_count": 143,
66
"metadata": {
77
"execution": {
8-
"iopub.execute_input": "2021-07-11T17:15:55.393544Z",
9-
"iopub.status.busy": "2021-07-11T17:15:55.393252Z",
10-
"iopub.status.idle": "2021-07-11T17:15:55.401945Z",
11-
"shell.execute_reply": "2021-07-11T17:15:55.401108Z",
12-
"shell.execute_reply.started": "2021-07-11T17:15:55.393469Z"
8+
"iopub.execute_input": "2021-07-26T02:25:15.792618Z",
9+
"iopub.status.busy": "2021-07-26T02:25:15.792309Z",
10+
"iopub.status.idle": "2021-07-26T02:25:26.923099Z",
11+
"shell.execute_reply": "2021-07-26T02:25:26.922160Z",
12+
"shell.execute_reply.started": "2021-07-26T02:25:15.792579Z"
1313
},
1414
"status": "ok",
1515
"tags": []
@@ -24,33 +24,48 @@
2424
}
2525
],
2626
"source": [
27+
"!sleep 11\n",
2728
"print(\"hello world!\")"
2829
]
2930
},
3031
{
3132
"cell_type": "code",
32-
"execution_count": null,
33+
"execution_count": 142,
3334
"metadata": {
3435
"execution": {
35-
"iopub.execute_input": "2021-07-11T17:31:43.432955Z",
36-
"iopub.status.busy": "2021-07-11T17:31:43.432660Z"
36+
"iopub.execute_input": "2021-07-26T02:23:54.629936Z",
37+
"iopub.status.busy": "2021-07-26T02:23:54.629690Z",
38+
"iopub.status.idle": "2021-07-26T02:25:00.767228Z",
39+
"shell.execute_reply": "2021-07-26T02:25:00.765983Z",
40+
"shell.execute_reply.started": "2021-07-26T02:23:54.629905Z"
3741
},
3842
"status": "ok",
3943
"tags": []
4044
},
41-
"outputs": [],
45+
"outputs": [
46+
{
47+
"name": "stdout",
48+
"output_type": "stream",
49+
"text": [
50+
"hello world!\n"
51+
]
52+
}
53+
],
4254
"source": [
4355
"!sleep 66\n",
4456
"print(\"hello world!\")"
4557
]
4658
},
4759
{
4860
"cell_type": "code",
49-
"execution_count": null,
61+
"execution_count": 99,
5062
"metadata": {
5163
"execution": {
52-
"iopub.execute_input": "2021-07-18T18:59:57.714531Z",
53-
"iopub.status.busy": "2021-07-18T18:59:57.714309Z"
64+
"iopub.execute_input": "2021-07-25T19:55:35.654800Z",
65+
"iopub.status.busy": "2021-07-25T19:55:35.654567Z",
66+
"iopub.status.idle": "2021-07-25T19:55:38.881008Z",
67+
"shell.execute_reply": "2021-07-25T19:55:38.879628Z",
68+
"shell.execute_reply.started": "2021-07-25T19:55:35.654769Z"
5469
},
5570
"tags": []
5671
},
@@ -62,7 +77,7 @@
6277
"traceback": [
6378
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
6479
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
65-
"\u001b[0;32m<ipython-input-2-d820d165a50b>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'sleep 3'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'hello world!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
80+
"\u001b[0;32m<ipython-input-99-d820d165a50b>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'sleep 3'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'hello world!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
6681
"\u001b[0;31mException\u001b[0m: hello world!"
6782
]
6883
}
@@ -72,6 +87,87 @@
7287
"raise Exception('hello world!')"
7388
]
7489
},
90+
{
91+
"cell_type": "code",
92+
"execution_count": 138,
93+
"metadata": {
94+
"execution": {
95+
"iopub.execute_input": "2021-07-26T02:15:24.129175Z",
96+
"iopub.status.busy": "2021-07-26T02:15:24.128902Z",
97+
"iopub.status.idle": "2021-07-26T02:15:28.256314Z",
98+
"shell.execute_reply": "2021-07-26T02:15:28.255193Z",
99+
"shell.execute_reply.started": "2021-07-26T02:15:24.129139Z"
100+
},
101+
"tags": []
102+
},
103+
"outputs": [
104+
{
105+
"name": "stdout",
106+
"output_type": "stream",
107+
"text": [
108+
"hola!\n"
109+
]
110+
}
111+
],
112+
"source": [
113+
"!sleep 4\n",
114+
"print(\"hola!\")"
115+
]
116+
},
117+
{
118+
"cell_type": "code",
119+
"execution_count": 139,
120+
"metadata": {
121+
"execution": {
122+
"iopub.execute_input": "2021-07-26T02:15:28.258778Z",
123+
"iopub.status.busy": "2021-07-26T02:15:28.258381Z",
124+
"iopub.status.idle": "2021-07-26T02:15:30.389469Z",
125+
"shell.execute_reply": "2021-07-26T02:15:30.388524Z",
126+
"shell.execute_reply.started": "2021-07-26T02:15:28.258744Z"
127+
},
128+
"tags": []
129+
},
130+
"outputs": [
131+
{
132+
"name": "stdout",
133+
"output_type": "stream",
134+
"text": [
135+
"bonjour!\n"
136+
]
137+
}
138+
],
139+
"source": [
140+
"!sleep 2\n",
141+
"print(\"bonjour!\")"
142+
]
143+
},
144+
{
145+
"cell_type": "code",
146+
"execution_count": 140,
147+
"metadata": {
148+
"execution": {
149+
"iopub.execute_input": "2021-07-26T02:15:30.391586Z",
150+
"iopub.status.busy": "2021-07-26T02:15:30.391285Z",
151+
"iopub.status.idle": "2021-07-26T02:15:33.519909Z",
152+
"shell.execute_reply": "2021-07-26T02:15:33.518958Z",
153+
"shell.execute_reply.started": "2021-07-26T02:15:30.391542Z"
154+
},
155+
"tags": []
156+
},
157+
"outputs": [
158+
{
159+
"name": "stdout",
160+
"output_type": "stream",
161+
"text": [
162+
"yo!\n"
163+
]
164+
}
165+
],
166+
"source": [
167+
"!sleep 3\n",
168+
"print(\"yo!\")"
169+
]
170+
},
75171
{
76172
"cell_type": "markdown",
77173
"metadata": {

0 commit comments

Comments
 (0)