Throwaway, experimental commit: adding a kernel ID to all cells.

Kernel ID doesn't get regenerated when the kernel is reset. Neither does
the client session ID. So this won't work for making sure that slices
don't slice into previous kernel sessions. The solution for that is
probably to stop including cells after the execution count hits 1 (or,
more simply, when the execution count stops decreasing).
This commit is contained in:
Andrew Head 2019-03-04 20:20:33 -08:00
Родитель fe6c1098af
Коммит 9598ec9606
8 изменённых файлов: 111 добавлений и 80 удалений

Просмотреть файл

@ -9,10 +9,14 @@ import { LocationSet, slice } from "./slice";
* A record of when a cell was executed.
*/
export class CellExecution {
constructor(
public readonly cell: ICell,
public readonly executionTime: Date
) { }
readonly cell: ICell;
readonly executionTime: Date;
constructor(cell: ICell, executionTime: Date) {
this.cell = cell;
this.executionTime = executionTime;
}
/**
* Update this method if at some point we only want to save some about a CellExecution when
@ -134,7 +138,7 @@ export class ExecutionLogSlicer {
.map(execution => {
// Build the program up to that cell.
let program = this._programBuilder.buildTo(execution.cell.persistentId, execution.cell.executionCount);
let program = this._programBuilder.buildTo(execution.cell.persistentId, execution.cell.kernelId, execution.cell.executionCount);
if (program == null) return null;
// Set the seed locations for the slice.

Просмотреть файл

@ -122,22 +122,18 @@ export class ProgramBuilder {
* Build a program from the list of cells. Program will include the cells' contents in
* execution order. It will omit cells that raised errors (syntax or runtime).
*/
buildTo(cellPersistentId: string, executionCount?: number): Program {
buildTo(cellPersistentId: string, kernelId: string, executionCount: number): Program {
let cellVersions = this._cellPrograms
.filter(cp => cp.cell.persistentId == cellPersistentId)
.map(cp => cp.cell);
let lastCell: ICell;
if (executionCount) {
lastCell = cellVersions.filter(cell => cell.executionCount == executionCount)[0];
} else {
lastCell = cellVersions.sort(
(cell1, cell2) => cell1.executionCount - cell2.executionCount
).pop();
}
let lastCell = cellVersions
.filter(cell => cell.executionCount == executionCount)
.filter(cell => cell.kernelId !== undefined && cell.kernelId === kernelId)[0];
if (!lastCell) return null;
let sortedCellPrograms = this._cellPrograms
.filter(cp => cp.cell.kernelId !== undefined && cp.cell.kernelId === kernelId)
.filter(cp => cp.cell == lastCell || !cp.hasError) // can have error only if it's the last cell
.filter(cp => cp.cell.executionCount != null && cp.cell.executionCount <= lastCell.executionCount)
.sort((cp1, cp2) => cp1.cell.executionCount - cp2.cell.executionCount);
@ -203,16 +199,11 @@ export class ProgramBuilder {
return new Program(code, tree, cellToLineMap, lineToCellMap);
}
build(): Program {
let lastCell = this._cellPrograms
.filter(cp => cp.cell.executionCount != null)
.sort((cp1, cp2) => cp1.cell.executionCount - cp2.cell.executionCount).pop();
return this.buildTo(lastCell.cell.persistentId);
}
getCellProgram(cell: ICell): CellProgram {
let matchingPrograms = this._cellPrograms.filter(
(cp) => cp.cell.persistentId == cell.persistentId && cp.cell.executionCount == cell.executionCount);
(cp) => cp.cell.persistentId == cell.persistentId &&
cp.cell.executionCount == cell.executionCount &&
cp.cell.kernelId !== undefined && cp.cell.kernelId === cell.kernelId);
if (matchingPrograms.length >= 1) return matchingPrograms.pop();
return null;
}

Просмотреть файл

@ -36,6 +36,13 @@ export interface ICell {
*/
text: string;
/**
* ID of the session that was last used to execute this cell. Note that unfortunately these
* session IDs reset whenever someone opens a new notebook---let's keep an eye out for ways
* to tie together sessions ID that should be merged.
*/
kernelId: string;
executionCount: number;
outputs: nbformat.IOutput[];
@ -80,6 +87,7 @@ export abstract class AbstractCell implements ICell {
abstract id: string;
abstract persistentId: string;
abstract executionCount: number;
abstract kernelId: string;
abstract hasError: boolean;
abstract isCode: boolean;
abstract text: string;
@ -148,19 +156,20 @@ export abstract class AbstractCell implements ICell {
*/
export class LogCell extends AbstractCell {
constructor(cellData: {
id?: string, persistentId?: string, executionCount?: number, hasError?: boolean,
text?: string, outputs?: nbformat.IOutput[]
constructor(data: {
id?: string, persistentId?: string, executionCount?: number, kernelId?: string,
hasError?: boolean, text?: string, outputs?: nbformat.IOutput[]
}) {
super();
this.is_cell = true;
this.id = cellData.id || UUID.uuid4();
this.persistentId = cellData.persistentId || UUID.uuid4();
this.executionCount = cellData.executionCount || undefined;
this.hasError = cellData.hasError || false;
this.text = cellData.text || "";
this.id = data.id || UUID.uuid4();
this.persistentId = data.persistentId || UUID.uuid4();
this.executionCount = data.executionCount || undefined;
this.kernelId = data.kernelId;
this.hasError = data.hasError || false;
this.text = data.text || "";
this.lastExecutedText = this.text;
this.outputs = cellData.outputs || [];
this.outputs = data.outputs || [];
this.gathered = false;
}
@ -172,6 +181,7 @@ export class LogCell extends AbstractCell {
readonly id: string;
readonly persistentId: string;
readonly executionCount: number;
readonly kernelId: string;
readonly hasError: boolean;
readonly isCode: boolean;
readonly text: string;
@ -222,6 +232,14 @@ export class LabCell extends AbstractCell {
this._model.metadata.set('last_executed_text', text);
}
get kernelId(): string {
return this._model.metadata.get('kernel_id') as string;
}
set kernelId(kernelId: string) {
this._model.metadata.set('kernel_id', kernelId);
}
get executionCount(): number {
return this._model.executionCount;
}

Просмотреть файл

@ -11,28 +11,44 @@ export class CellChangeListener {
private _gatherModel: GatherModel;
constructor(gatherModel: GatherModel, notebookPanel: NotebookPanel) {
constructor(gatherModel: GatherModel, notebook: NotebookPanel) {
this._gatherModel = gatherModel;
this.registerCurrentCells(notebookPanel);
notebookPanel.content.model.cells.changed.connect((_, change) => this.registerAddedCells(change), this);
this._notebook = notebook;
this._registerCurrentCells(notebook);
notebook.content.model.cells.changed.connect((_, change) => this._registerAddedCells(change), this);
}
private registerCurrentCells(notebookPanel: NotebookPanel) {
private _registerCurrentCells(notebookPanel: NotebookPanel) {
for (let i = 0; i < notebookPanel.content.model.cells.length; i++) {
this.registerCell(notebookPanel.content.model.cells.get(i));
this._registerCell(notebookPanel.content.model.cells.get(i));
}
}
private registerCell(cell: ICellModel) {
/**
* It's expected that this is called directly after the cell is executed.
*/
private _annotateCellWithExecutionInformation(cell: LabCell) {
cell.lastExecutedText = cell.text;
cell.kernelId = this._notebook.session.kernel.model.id;
this._notebook.session.kernel.requestHistory({ output: false, raw: true, hist_access_type: 'tail' }).then((res) => {
console.log("Gotta reply");
});
}
private _registerCell(cell: ICellModel) {
if (cell.type !== 'code') { return; }
/**
/*
* A cell will be considered edited whenever any of its contents changed, including
* execution count, metadata, outputs, text, etc.
*/
cell.stateChanged.connect((changedCell, cellStateChange) => {
if (cellStateChange.name === "executionCount" && cellStateChange.newValue !== undefined && cellStateChange.newValue !== null) {
let labCell = new LabCell(changedCell as ICodeCellModel);
labCell.lastExecutedText = labCell.text;
/*
* Annotate the cell before reporting to the model that it was executed, because
* the model's listeners will need these annotations.
*/
this._annotateCellWithExecutionInformation(labCell);
this._gatherModel.lastExecutedCell = labCell;
}
});
@ -43,11 +59,11 @@ export class CellChangeListener {
});
}
public registerAddedCells(cellListChange: IObservableList.IChangedArgs<ICellModel>): void {
private _registerAddedCells(cellListChange: IObservableList.IChangedArgs<ICellModel>): void {
if (cellListChange.type === 'add' || cellListChange.type === 'remove') {
const cellModel = cellListChange.newValues[0] as ICellModel;
if (cellListChange.type === 'add') {
this.registerCell(cellModel);
this._registerCell(cellModel);
} else if (cellListChange.type === 'remove') {
if (cellModel instanceof CodeCellModel) {
this._gatherModel.lastDeletedCell = new LabCell(cellModel);
@ -55,4 +71,6 @@ export class CellChangeListener {
}
}
}
private _notebook: NotebookPanel;
}

Просмотреть файл

@ -274,7 +274,7 @@ export class MarkerManager implements IGatherObserver {
* analyze the code here.
*/
let cellProgram = this._model.getCellProgram(cell);
if (!cellProgram.hasError) {
if (cellProgram !== null && !cellProgram.hasError) {
for (let ref of cellProgram.defs) {
if (ref.type == SymbolType.VARIABLE) {
this._model.addEditorDef({ def: ref, editor: editor, cell: cell });

Просмотреть файл

@ -91,6 +91,7 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
let id = _getString(cellJson, 'id');
let persistentId = _getString(cellJson, 'persistentId');
let executionCount = _getNumber(cellJson, 'executionCount');
let kernelId = _getString(cellJson, 'kernelId');
let hasError = _getBoolean(cellJson, 'hasError');
let text = _getString(cellJson, 'text');
@ -98,7 +99,7 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
let executionTime = new Date(executionTimeString);
if (id == null || executionCount == null || hasError == null ||
text == null || executionTime == null) {
kernelId == null || text == null || executionTime == null) {
log("Cell could not be loaded, as it's missing a critical field.");
return null;
}

Просмотреть файл

@ -12,6 +12,7 @@ interface CellJson extends JSONObject {
id: string;
persistentId: string;
executionCount: number;
kernelId: string;
hasError: boolean;
isCode: boolean;
text: string;
@ -33,6 +34,7 @@ export function storeHistory(notebookModel: INotebookModel, executionLog: Execut
cellJson.id = cell.id;
cellJson.persistentId = cell.persistentId;
cellJson.executionCount = cell.executionCount;
cellJson.kernelId = cell.kernelId;
cellJson.hasError = cell.hasError;
cellJson.text = cell.text;

Просмотреть файл

@ -5,9 +5,16 @@ import { ICell, LogCell } from '../model/cell';
describe('program builder', () => {
const TEST_KERNEL_ID = "kernel-1";
function createCellWithKernelId(persistentId: string, kernelId: string, executionCount: number,
...codeLines: string[]): ICell {
let text = codeLines.join("\n");
return new LogCell({ executionCount, persistentId, kernelId, text });
}
function createCell(persistentId: string, executionCount: number, ...codeLines: string[]): ICell {
let text = codeLines.join("\n");
return new LogCell({ executionCount, persistentId, text });
return(createCellWithKernelId(persistentId, TEST_KERNEL_ID, executionCount, ...codeLines));
}
let programBuilder: ProgramBuilder;
@ -20,7 +27,7 @@ describe('program builder', () => {
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
)
let code = programBuilder.build().text;
let code = programBuilder.buildTo("id1", TEST_KERNEL_ID, 2).text;
expect(code).to.equal(["print(2)", "print(1)", ""].join("\n"))
});
@ -28,7 +35,7 @@ describe('program builder', () => {
let cell1 = createCell("id1", 1, "print(1)");
let cell2 = createCell("id2", 2, "print(2)");
programBuilder.add(cell1, cell2);
let lineToCellMap = programBuilder.buildTo("id2").lineToCellMap;
let lineToCellMap = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).lineToCellMap;
expect(lineToCellMap[1]).to.equal(cell1);
expect(lineToCellMap[2]).to.equal(cell2);
});
@ -37,40 +44,20 @@ describe('program builder', () => {
let cell1 = createCell("id1", 1, "print(1)");
let cell2 = createCell("id2", 2, "print(2)");
programBuilder.add(cell1, cell2);
let cellToLineMap = programBuilder.buildTo("id2").cellToLineMap;
let cellToLineMap = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).cellToLineMap;
expect(cellToLineMap["id1"][1].items).to.deep.equal([1]);
expect(cellToLineMap["id2"][2].items).to.deep.equal([2]);
});
it('stops after the specified cell\'s ID', () => {
it('stops after the specified cell', () => {
programBuilder.add(
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
);
let code = programBuilder.buildTo("id2").text;
let code = programBuilder.buildTo("id2", TEST_KERNEL_ID, 1).text;
expect(code).to.equal("print(2)\n");
});
it('builds to the most recent version of the cell', () => {
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)"),
createCell("id1", 3, "print(3)") // cell id1 run twice
);
let code = programBuilder.buildTo("id1").text;
expect(code).to.equal(["print(1)", "print(2)", "print(3)", ""].join("\n"));
});
it('builds to a requested version of a cell', () => {
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)"),
createCell("id1", 3, "print(3)") // cell id1 run twice
);
let code = programBuilder.buildTo("id1", 1).text;
expect(code).to.equal("print(1)\n");
});
/* We might want the program builder to include code that was executed before a runtime
* error, though this will probably require us to rewrite the code. */
it('skips cells with errors', () => {
@ -81,7 +68,7 @@ describe('program builder', () => {
badCell,
createCell("id3", 3, "print(3)")
);
let code = programBuilder.buildTo("id3").text;
let code = programBuilder.buildTo("id3", TEST_KERNEL_ID, 3).text;
expect(code).to.equal(["print(1)", "print(3)", ""].join("\n"));
});
@ -93,7 +80,7 @@ describe('program builder', () => {
createCell("id2", 2, "print(2)"),
badCell,
);
let code = programBuilder.build().text;
let code = programBuilder.buildTo("idE", TEST_KERNEL_ID, 3).text;
expect(code).to.equal(["print(1)", "print(2)", "print(bad_name)", ""].join("\n"));
});
@ -115,25 +102,35 @@ describe('program builder', () => {
// Restore console output.
console.log = oldConsoleLog;
let code = programBuilder.buildTo("id3").text;
let code = programBuilder.buildTo("id3", TEST_KERNEL_ID, 3).text;
expect(code).to.equal(["print(1)", "print(3)", ""].join("\n"));
});
it('skips cells that were executed with different kernels', () => {
programBuilder.add(
createCellWithKernelId("id1", "kernel-1", 1, "print(1)"),
createCellWithKernelId("id2", "kernel-2", 2, "print(2)"),
createCellWithKernelId("id3", "kernel-1", 3, "print(3)")
);
let code = programBuilder.buildTo("id3", "kernel-1", 3).text;
expect(code).to.equals(["print(1)", "print(3)", ""].join("\n"));
});
it('constructs a tree for the program', () => {
programBuilder.add(
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)")
)
let tree = programBuilder.build().tree;
let tree = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).tree;
expect(tree.code.length).to.equal(2);
});
it('adjusts the node locations', () => {
programBuilder.add(
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)")
)
let tree = programBuilder.build().tree;
let tree = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).tree;
expect(tree.code[0].location.first_line).to.equal(1);
expect(tree.code[1].location.first_line).to.equal(2);
});
@ -142,7 +139,7 @@ describe('program builder', () => {
programBuilder.add(
createCell("id1", 2, "print(1)")
);
let tree = programBuilder.build().tree;
let tree = programBuilder.buildTo("id1", TEST_KERNEL_ID, 2).tree;
expect(tree.code[0].cellPersistentId).to.equal("id1");
expect(tree.code[0].executionCount).to.equal(2);
});