From 297281bc5bc1ef98d10ab4337d83c9f87cc0a5dd Mon Sep 17 00:00:00 2001 From: mcrutch <51446717+mcrutch@users.noreply.github.com> Date: Fri, 20 Aug 2021 16:31:16 +0000 Subject: [PATCH 1/3] WIP to do cell execution tracking. Lots of cleanup to do but don't want to risk losing the code --- instrumentation/package.json | 66 ++++++++++++++++++++ instrumentation/src/index.ts | 103 +++++++++++++++++++++++++++++++ instrumentation/tsconfig.json | 24 +++++++ jupyterlab_nbgallery/_version.py | 2 +- package.json | 3 +- setup.py | 3 +- 6 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 instrumentation/package.json create mode 100644 instrumentation/src/index.ts create mode 100644 instrumentation/tsconfig.json diff --git a/instrumentation/package.json b/instrumentation/package.json new file mode 100644 index 0000000..195accb --- /dev/null +++ b/instrumentation/package.json @@ -0,0 +1,66 @@ +{ + "name": "@jupyterlab-nbgallery/instrumentation", + "version": "0.2.0", + "description": "Track cell execution Metrics", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/nbgallery/lab-extensions", + "bugs": { + "url": "https://github.com/nbgallery/lab-extensions/issues" + }, + "license": "MIT", + "author": "Team@NBG", + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", + "schema/**/*.json" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/nbgallery/lab-extensions" + }, + "scripts": { + "build": "jlpm run build:lib", + "build:labextension": "mkdir -p ../labextension && mkdir -p labextension && cd labextension && npm pack .. && cp *.tgz ../../labextension/", + "build:lib": "tsc", + "build:all": "jlpm run build:labextension", + "clean": "jlpm run clean:lib", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "clean:labextension": "rimraf labextension", + "install-ext": "jupyter labextension install . --no-build", + "prepare": "jlpm run clean && jlpm run build", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "watch": "tsc -w" + }, + "dependencies": { + "@jupyterlab/notebook": "^3.1.4", + "@jupyterlab/settingregistry": "^2.0.0 || ^3.0.0", + "@types/jquery": "^3.5.0", + "jquery": "^3.5.0", + "ts-md5": "^1.2.9" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.21.0", + "@typescript-eslint/parser": "^2.21.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-jsdoc": "^22.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.18.3", + "rimraf": "^3.0.0", + "typescript": "~3.7.5", + "glob": "latest" + }, + "sideEffects": [ + "style/*.css" + ], + "jupyterlab": { + "extension": true + } +} diff --git a/instrumentation/src/index.ts b/instrumentation/src/index.ts new file mode 100644 index 0000000..dded9a7 --- /dev/null +++ b/instrumentation/src/index.ts @@ -0,0 +1,103 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +import { + ISettingRegistry +} from '@jupyterlab/settingregistry'; + +import { NotebookActions, Notebook } from '@jupyterlab/notebook'; +import { Cell, CodeCell } from '@jupyterlab/cells'; +import {Md5} from 'ts-md5/dist/md5' +import $ from 'jquery'; + +interface executionTracking{ + startTime: number; + cellIndex: number; +} +interface CellTracking { + [cellid: string]: executionTracking; +} + +interface executionRecord{ + uuid: string; + md5: string; + success: boolean; + runtime: number; +} + + +function transmit_execution( notebook: Notebook, cell: Cell, success: boolean, runtime: number){ + let gallery_metadata :any; + gallery_metadata = notebook.model.metadata.toJSON()["gallery"]; + if (gallery_metadata){ + let log = new Object() as executionRecord; + log["success"] = success; + log["md5"] = Md5.hashStr(cell.model.value.text); + log["runtime"] = runtime; + log["uuid"] = gallery_metadata["uuid"] || gallery_metadata["link"] || gallery_metadata["clone"]; + let url = gallery_metadata["gallery_url"]; + console.log(url); + if(url.length>0 && log["uuid"].length>0){ + $.ajax({ + method: "POST", + headers: {Accept: "application/json"}, + url: url + "/executions", + data: log, + xhrFields: { withCredentials: true } + }); + } + console.log("Made it here" + notebook + cell + success + runtime); + console.log(gallery_metadata["uuid"]); + } +} + + +/** + * Initialization data for the hello-world extension. + */ +const extension: JupyterFrontEndPlugin = { + id: 'instrumentation', + autoStart: true, + requires: [ISettingRegistry], + activate: async (app: JupyterFrontEnd, + settings: ISettingRegistry + ) => { + + + let tracker: CellTracking = {}; + //let cells = new Object(); + /*let nbgallery_url = ""; + let enabled = true; + const cellExecutionMetadataTable: LRU< + string, + ICellExecutionMetadata + > = new LRU({ + max: 500 * 5 // to save 500 notebooks x 5 cells + });*/ + NotebookActions.executionScheduled.connect((_, args) => { + let cell: Cell; + let notebook: Notebook; + notebook = args["notebook"]; + cell = args ["cell"]; + const started = new Date(); + tracker[cell.id] = new Object() as executionTracking; + tracker[cell.id].startTime = started.getTime(); + tracker[cell.id].cellIndex = notebook.activeCellIndex; + console.log(cell); + }); + + NotebookActions.executed.connect((_, args) => { + const { cell, notebook, success } = args; + if (cell instanceof CodeCell) { + const finished = new Date(); + console.log("Post execution"); + transmit_execution(notebook, cell, success, (finished.getTime() - tracker[cell.id].startTime) ); + } + }); + + } +}; + +export default extension; diff --git a/instrumentation/tsconfig.json b/instrumentation/tsconfig.json new file mode 100644 index 0000000..81139f5 --- /dev/null +++ b/instrumentation/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "esModuleInterop": true, + "incremental": true, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "strictNullChecks": false, + "target": "es2017", + "types": [] + }, + "include": ["src/*"] +} diff --git a/jupyterlab_nbgallery/_version.py b/jupyterlab_nbgallery/_version.py index 0ec85e8..3bcc241 100644 --- a/jupyterlab_nbgallery/_version.py +++ b/jupyterlab_nbgallery/_version.py @@ -1,2 +1,2 @@ -version_info = (0, 2, 1) +version_info = (0, 2, 2) __version__ = ".".join(map(str, version_info)) diff --git a/package.json b/package.json index c9e46f8..3fbad04 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "environment-registration", "environment-life", "autodownload", - "gallerymenu" + "gallerymenu", + "instrumentation" ] }, "devDependencies": { diff --git a/setup.py b/setup.py index 58b7453..3db5b84 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ def run(self): pjoin(HERE, "environment-registration", "lib", "index.js"), pjoin(HERE, "autodownload", "lib", "index.js"), pjoin(HERE, "gallerymenu", "lib", "index.js"), + pjoin(HERE, "instrumentation", "lib", "index.js"), ] package_data_spec = { @@ -88,7 +89,7 @@ def run(self): cmdclass= cmdclass, packages=setuptools.find_packages(), install_requires=[ - "jupyterlab>=2.0", + "jupyterlab>=3.1.0", "jupyter-nbgallery~=2.0", ], zip_safe=False, From c2277cb10c9302f179f5618c9ae6fb274430fea4 Mon Sep 17 00:00:00 2001 From: mcrutch <51446717+mcrutch@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:02:50 +0000 Subject: [PATCH 2/3] Cleanup code, add ability to read the environment variable for instrumentation --- instrumentation/package.json | 12 ++-- instrumentation/schema/instrumentation.json | 13 ++++ instrumentation/src/index.ts | 66 ++++++++++++++------- jupyterlab_nbgallery/handlers.py | 12 +++- 4 files changed, 73 insertions(+), 30 deletions(-) create mode 100644 instrumentation/schema/instrumentation.json diff --git a/instrumentation/package.json b/instrumentation/package.json index 195accb..6dfb12a 100644 --- a/instrumentation/package.json +++ b/instrumentation/package.json @@ -1,6 +1,6 @@ { "name": "@jupyterlab-nbgallery/instrumentation", - "version": "0.2.0", + "version": "0.2.2", "description": "Track cell execution Metrics", "keywords": [ "jupyter", @@ -39,8 +39,8 @@ "watch": "tsc -w" }, "dependencies": { - "@jupyterlab/notebook": "^3.1.4", - "@jupyterlab/settingregistry": "^2.0.0 || ^3.0.0", + "@jupyterlab/notebook": "^3.1.0", + "@jupyterlab/settingregistry": "^3.1.0", "@types/jquery": "^3.5.0", "jquery": "^3.5.0", "ts-md5": "^1.2.9" @@ -57,10 +57,8 @@ "typescript": "~3.7.5", "glob": "latest" }, - "sideEffects": [ - "style/*.css" - ], "jupyterlab": { - "extension": true + "extension": true, + "schemaDir": "schema" } } diff --git a/instrumentation/schema/instrumentation.json b/instrumentation/schema/instrumentation.json new file mode 100644 index 0000000..c446c05 --- /dev/null +++ b/instrumentation/schema/instrumentation.json @@ -0,0 +1,13 @@ +{ + "title": "Instrumentation Settings", + "description": "Should Jupyter report cell execution to NBGallery?", + "type": "object", + "properties":{ + "enabled": { + "type": "boolean", + "title": "Is instrumentation enabled?", + "description": "Should Jupyter send execution data to NBGallery?", + "default": false + } + } +} diff --git a/instrumentation/src/index.ts b/instrumentation/src/index.ts index dded9a7..30d7c3c 100644 --- a/instrumentation/src/index.ts +++ b/instrumentation/src/index.ts @@ -65,39 +65,63 @@ const extension: JupyterFrontEndPlugin = { settings: ISettingRegistry ) => { - let tracker: CellTracking = {}; - //let cells = new Object(); - /*let nbgallery_url = ""; - let enabled = true; - const cellExecutionMetadataTable: LRU< - string, - ICellExecutionMetadata - > = new LRU({ - max: 500 * 5 // to save 500 notebooks x 5 cells - });*/ + let enabled = false; + + function get_url(){ + return window.location.href.replace(/\/lab.*$/g,"/"); + } + + function instrumentation(setting: ISettingRegistry.ISettings){ + $.ajax({ + method: 'GET', + headers: { Accept: 'application/json' }, + url: get_url() + 'jupyterlab_nbgallery/instrumentation', + cache: false, + xhrFields: {withCredentials: true}, + success: function(environment) { + if (environment['NBGALLERY_ENABLE_INSTRUMENTATION'] == 1 || (setting.get('enabled').composite as boolean)){ + setting.set("enabled",true); + enabled = true; + }else{ + enabled = false; + } + } + }); + } + NotebookActions.executionScheduled.connect((_, args) => { - let cell: Cell; - let notebook: Notebook; - notebook = args["notebook"]; - cell = args ["cell"]; - const started = new Date(); - tracker[cell.id] = new Object() as executionTracking; - tracker[cell.id].startTime = started.getTime(); - tracker[cell.id].cellIndex = notebook.activeCellIndex; - console.log(cell); + if(enabled){ + let cell: Cell; + let notebook: Notebook; + notebook = args["notebook"]; + cell = args ["cell"]; + const started = new Date(); + tracker[cell.id] = new Object() as executionTracking; + tracker[cell.id].startTime = started.getTime(); + tracker[cell.id].cellIndex = notebook.activeCellIndex; + console.log(cell); + } }); NotebookActions.executed.connect((_, args) => { const { cell, notebook, success } = args; - if (cell instanceof CodeCell) { + if (enabled && cell instanceof CodeCell) { const finished = new Date(); console.log("Post execution"); transmit_execution(notebook, cell, success, (finished.getTime() - tracker[cell.id].startTime) ); } }); - + Promise.all([app.restored, settings.load('@jupyterlab-nbgallery/instrumentation:instrumentation')]) + .then(([, setting]) => { + try { + instrumentation(setting); + } catch(reason) { + console.error(`Problem initializing instrumentation \n ${reason}`); + } + }); } }; + export default extension; diff --git a/jupyterlab_nbgallery/handlers.py b/jupyterlab_nbgallery/handlers.py index 841e84a..115dea0 100644 --- a/jupyterlab_nbgallery/handlers.py +++ b/jupyterlab_nbgallery/handlers.py @@ -6,7 +6,6 @@ import tornado - class EnvironmentHandler(APIHandler): # The following decorator should be present on all verb methods (head, get, post, # patch, put, delete, options) to ensure only authorized user can request the @@ -15,6 +14,14 @@ class EnvironmentHandler(APIHandler): def get(self): self.finish(json.dumps({"NBGALLERY_URL" : os.getenv("NBGALLERY_URL"), "NBGALLERY_CLIENT_NAME" : os.getenv("NBGALLERY_CLIENT_NAME"), "NBGALLERY_ENABLE_AUTODOWNLOAD" : os.getenv("NBGALLERY_ENABLE_AUTODOWNLOAD") })) +class InstrumentationHandler(APIHandler): + # The following decorator should be present on all verb methods (head, get, post, + # patch, put, delete, options) to ensure only authorized user can request the + # Jupyter server + @tornado.web.authenticated + def get(self): + self.finish(json.dumps({"NBGALLERY_ENABLE_INSTRUMENTATION" : os.getenv("NBGALLERY_ENABLE_INSTRUMENTATION")})) + class ExpirationHandler(APIHandler): # The following decorator should be present on all verb methods (head, get, post, # patch, put, delete, options) to ensure only authorized user can request the @@ -31,5 +38,6 @@ def setup_handlers(web_app, url_path): # Prepend the base_url so that it works in a jupyterhub setting environment_pattern = url_path_join(base_url, url_path, "environment") expiration_pattern = url_path_join(base_url, url_path, "expiration") - handlers = [(environment_pattern, EnvironmentHandler),(expiration_pattern, ExpirationHandler)] + instrumentation_pattern = url_path_join(base_url, url_path, "instrumentation") + handlers = [(environment_pattern, EnvironmentHandler),(expiration_pattern, ExpirationHandler),(instrumentation_pattern, InstrumentationHandler)] web_app.add_handlers(host_pattern, handlers) From 61e2d1adea2c876bf9817aade5959f0179eb6cf2 Mon Sep 17 00:00:00 2001 From: mcrutch <51446717+mcrutch@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:17:07 +0000 Subject: [PATCH 3/3] Setting version numbering Only extension missing at this point is Easybuttons --- instrumentation/package.json | 2 +- jupyterlab_nbgallery/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/package.json b/instrumentation/package.json index 6dfb12a..a0b2b51 100644 --- a/instrumentation/package.json +++ b/instrumentation/package.json @@ -1,6 +1,6 @@ { "name": "@jupyterlab-nbgallery/instrumentation", - "version": "0.2.2", + "version": "0.3.0", "description": "Track cell execution Metrics", "keywords": [ "jupyter", diff --git a/jupyterlab_nbgallery/_version.py b/jupyterlab_nbgallery/_version.py index 3bcc241..4d32877 100644 --- a/jupyterlab_nbgallery/_version.py +++ b/jupyterlab_nbgallery/_version.py @@ -1,2 +1,2 @@ -version_info = (0, 2, 2) +version_info = (0, 3, 0) __version__ = ".".join(map(str, version_info))