Skip to content

Commit

Permalink
Merge pull request #13 from nbgallery/execution_tracking
Browse files Browse the repository at this point in the history
WIP to do cell execution tracking.  Lots of cleanup to do but don't w…
  • Loading branch information
mcrutch authored Aug 23, 2021
2 parents 660096e + 61e2d1a commit ae8e9cb
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 5 deletions.
64 changes: 64 additions & 0 deletions instrumentation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "@jupyterlab-nbgallery/instrumentation",
"version": "0.3.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.0",
"@jupyterlab/settingregistry": "^3.1.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"
},
"jupyterlab": {
"extension": true,
"schemaDir": "schema"
}
}
13 changes: 13 additions & 0 deletions instrumentation/schema/instrumentation.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
127 changes: 127 additions & 0 deletions instrumentation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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<void> = {
id: 'instrumentation',
autoStart: true,
requires: [ISettingRegistry],
activate: async (app: JupyterFrontEnd,
settings: ISettingRegistry
) => {

let tracker: CellTracking = {};
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) => {
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 (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;
24 changes: 24 additions & 0 deletions instrumentation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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/*"]
}
2 changes: 1 addition & 1 deletion jupyterlab_nbgallery/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version_info = (0, 2, 1)
version_info = (0, 3, 0)
__version__ = ".".join(map(str, version_info))
12 changes: 10 additions & 2 deletions jupyterlab_nbgallery/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"environment-registration",
"environment-life",
"autodownload",
"gallerymenu"
"gallerymenu",
"instrumentation"
]
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit ae8e9cb

Please sign in to comment.