diff --git a/packages/cells/src/widget.ts b/packages/cells/src/widget.ts index b88ed62c7157..b70787460991 100644 --- a/packages/cells/src/widget.ts +++ b/packages/cells/src/widget.ts @@ -797,17 +797,48 @@ export namespace CodeCell { model.outputs.clear(); return Promise.resolve(void 0); } - - let cellId = { cellId: model.id }; - metadata = { ...metadata, ...cellId }; + metadata = { ...metadata, cellId: model.id }; + const { recordTiming } = metadata; model.executionCount = null; cell.outputHidden = false; cell.setPrompt('*'); model.trusted = true; - return OutputArea.execute(code, cell.outputArea, session, metadata) + const p = OutputArea.execute(code, cell.outputArea, session, metadata); + // cell.outputArea.future assigned synchronously in `execute` + if (recordTiming) { + cell.outputArea.future.onIOPub = (msg: KernelMessage.IIOPubMessage) => { + let label: string; + switch (msg.header.msg_type) { + case 'status': + label = `status.${msg.content.execution_state}`; + break; + case 'execute_input': + label = 'execute_input'; + break; + default: + return; + } + const value = msg.header.date; + if (!value) { + return; + } + model.metadata.set(`timing.iopub.${label}`, value); + }; + } + + return p .then(msg => { model.executionCount = msg.content.execution_count; + const started = msg.metadata.started as string; + if (recordTiming && started) { + model.metadata.set('timing.shell.execute_reply.started', started); + } + const date = msg.header.date as string; + if (recordTiming && date) { + model.metadata.set('timing.shell.execute_reply', date); + } + return msg; }) .catch(e => { diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts index c29b62fd6d0b..b84e54fbfbd8 100644 --- a/packages/notebook-extension/src/index.ts +++ b/packages/notebook-extension/src/index.ts @@ -157,6 +157,8 @@ namespace CommandIDs { export const toggleAllLines = 'notebook:toggle-all-cell-line-numbers'; + export const toggleRecordTiming = 'notebook:toggle-record-timing'; + export const undoCellAction = 'notebook:undo-cell-action'; export const redoCellAction = 'notebook:redo-cell-action'; @@ -1284,6 +1286,17 @@ function addCommands( }, isEnabled }); + commands.addCommand(CommandIDs.toggleRecordTiming, { + label: 'Toggle Recording Cell Timing', + execute: args => { + const current = getCurrent(args); + + if (current) { + return NotebookActions.toggleRecordTiming(current.content); + } + }, + isEnabled + }); commands.addCommand(CommandIDs.commandMode, { label: 'Enter Command Mode', execute: args => { @@ -1616,6 +1629,7 @@ function populatePalette( CommandIDs.deselectAll, CommandIDs.clearAllOutputs, CommandIDs.toggleAllLines, + CommandIDs.toggleRecordTiming, CommandIDs.editMode, CommandIDs.commandMode, CommandIDs.changeKernel, diff --git a/packages/notebook/src/actions.tsx b/packages/notebook/src/actions.tsx index 40bee26d2424..65a540945033 100644 --- a/packages/notebook/src/actions.tsx +++ b/packages/notebook/src/actions.tsx @@ -913,6 +913,19 @@ export namespace NotebookActions { Private.handleState(notebook, state); } + /** + * Toggle whether to record cell timing execution. + * + * @param notebook - The target notebook widget. + */ + export function toggleRecordTiming(notebook: Notebook): void { + if (!notebook.model) { + return; + } + const currentValue = notebook.model.metadata.get('record_timing') || false; + notebook.model.metadata.set('record_timing', !currentValue); + } + /** * Clear the code outputs of the selected cells. * @@ -1454,7 +1467,8 @@ namespace Private { case 'code': if (session) { return CodeCell.execute(cell as CodeCell, session, { - deletedCells: notebook.model.deletedCells + deletedCells: notebook.model.deletedCells, + recordTiming: notebook.model.metadata.get('record_timing') || false }) .then(reply => { notebook.model.deletedCells.splice( diff --git a/tests/test-cells/src/widget.spec.ts b/tests/test-cells/src/widget.spec.ts index f53522bb3545..974c470169d9 100644 --- a/tests/test-cells/src/widget.spec.ts +++ b/tests/test-cells/src/widget.spec.ts @@ -461,6 +461,29 @@ describe('cells/widget', () => { const executionCount = widget.model.executionCount; expect(executionCount).to.not.equal(originalCount); }); + + const TIMING_KEYS = [ + 'timing.iopub.execute_input', + 'timing.shell.execute_reply.started', + 'timing.shell.execute_reply', + 'timing.iopub.status.busy', + 'timing.iopub.status.idle' + ]; + + it('should not save timing info by default', async () => { + const widget = new CodeCell({ model, rendermime, contentFactory }); + await CodeCell.execute(widget, session); + for (const key of TIMING_KEYS) { + expect(widget.model.metadata.get(key)).to.not.exist; + } + }); + it('should save timing info if requested', async () => { + const widget = new CodeCell({ model, rendermime, contentFactory }); + await CodeCell.execute(widget, session, { recordTiming: true }); + for (const key of TIMING_KEYS) { + expect(widget.model.metadata.get(key)).to.exist; + } + }); }); });