From e8de662b4d8d5e4bcc7368277d8607f9a8bc3405 Mon Sep 17 00:00:00 2001 From: Alasdair Armstrong Date: Tue, 31 Oct 2017 17:32:33 +0000 Subject: Added trace viewer application for traces produced by sail -ocaml_trace See README file for how to set up and use --- src/trace_viewer/.gitignore | 6 + src/trace_viewer/List-add.svg | 56 ++++++++ src/trace_viewer/List-remove.svg | 56 ++++++++ src/trace_viewer/README | 11 ++ src/trace_viewer/index.css | 86 ++++++++++++ src/trace_viewer/index.html | 19 +++ src/trace_viewer/index.ts | 287 +++++++++++++++++++++++++++++++++++++++ src/trace_viewer/main.ts | 12 ++ src/trace_viewer/package.json | 15 ++ src/trace_viewer/tsconfig.json | 18 +++ 10 files changed, 566 insertions(+) create mode 100644 src/trace_viewer/.gitignore create mode 100644 src/trace_viewer/List-add.svg create mode 100644 src/trace_viewer/List-remove.svg create mode 100644 src/trace_viewer/README create mode 100644 src/trace_viewer/index.css create mode 100644 src/trace_viewer/index.html create mode 100644 src/trace_viewer/index.ts create mode 100644 src/trace_viewer/main.ts create mode 100644 src/trace_viewer/package.json create mode 100644 src/trace_viewer/tsconfig.json diff --git a/src/trace_viewer/.gitignore b/src/trace_viewer/.gitignore new file mode 100644 index 00000000..c1f9aea6 --- /dev/null +++ b/src/trace_viewer/.gitignore @@ -0,0 +1,6 @@ +*~ +*.js +*.js.map + +# Dependencies +node_modules/ diff --git a/src/trace_viewer/List-add.svg b/src/trace_viewer/List-add.svg new file mode 100644 index 00000000..f8031599 --- /dev/null +++ b/src/trace_viewer/List-add.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Add + 2006-01-04 + + + Andreas Nilsson + + + http://tango-project.org + + + add + plus + + + + + + + + + + + + + + + + + + + + + diff --git a/src/trace_viewer/List-remove.svg b/src/trace_viewer/List-remove.svg new file mode 100644 index 00000000..f8031599 --- /dev/null +++ b/src/trace_viewer/List-remove.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Add + 2006-01-04 + + + Andreas Nilsson + + + http://tango-project.org + + + add + plus + + + + + + + + + + + + + + + + + + + + + diff --git a/src/trace_viewer/README b/src/trace_viewer/README new file mode 100644 index 00000000..547a1435 --- /dev/null +++ b/src/trace_viewer/README @@ -0,0 +1,11 @@ + +To use, first make sure node.js and npm are installed (e.g. via the +Ubuntu package manager), then run the following in this directory: + +> npm install + +> npm run tsc + +> ./node_modules/.bin/electron . + +and point the file selector at a trace produced by sail -ocaml_trace \ No newline at end of file diff --git a/src/trace_viewer/index.css b/src/trace_viewer/index.css new file mode 100644 index 00000000..35ebcb23 --- /dev/null +++ b/src/trace_viewer/index.css @@ -0,0 +1,86 @@ + +body { + background-color: #202020; + color: #DCDCCC; + font-family: monospace; + font-size: 14pt; + font-weight: bold; +} + +img { + height: 30px; +} + +#control { + position: fixed; + bottom: 0px; + left:10%; + right:10%; + width:80%; +} + +#command { + font-size: 16pt; + width: 100%; +} + +.call { + background-color: #313131; + border: 1px; + border-left: 5px; + border-color: rgb(118, 173, 160); + border-style: solid; + padding-top: 2px; + padding-bottom: 2px; + margin: 0px; + min-height: 32px; + display: flex; + align-items: center; +} + +.write { + background-color: #313131; + border: 1px; + border-left: 5px; + border-color: rgb(255, 40, 40); + border-style: solid; + padding-top: 2px; + padding-bottom: 2px; + margin: 0px; + min-height: 32px; + display: flex; + align-items: center; +} + +.load { + background-color: #313131; + color: white; + border: 1px; + border-left: 5px; + border-color: #ff9100; + border-style: solid; + padding-top: 2px; + padding-bottom: 2px; + margin: 0px; + min-height: 32px; + display: flex; + align-items: center; +} + +.read { + background-color: #313131; + border: 1px; + border-left: 5px; + border-color: rgb(107, 199, 47); + border-style: solid; + padding-top: 2px; + padding-bottom: 2px; + margin: 0px; + min-height: 32px; + display: flex; + align-items: center; +} + +.tree { + padding-left: 20px; +} \ No newline at end of file diff --git a/src/trace_viewer/index.html b/src/trace_viewer/index.html new file mode 100644 index 00000000..75c5e035 --- /dev/null +++ b/src/trace_viewer/index.html @@ -0,0 +1,19 @@ + + + + + Sail Log Viewer + + + + + +
+
+
+ +
+ + \ No newline at end of file diff --git a/src/trace_viewer/index.ts b/src/trace_viewer/index.ts new file mode 100644 index 00000000..f9b5041b --- /dev/null +++ b/src/trace_viewer/index.ts @@ -0,0 +1,287 @@ +import {remote} from "electron" +import fs = require("fs") +const dialog = remote.dialog +const app = remote.app + +let topCallDiv = document.createElement("div") + +const max_arg_length = 5000 + +abstract class Event { + caller: Call + + protected div: HTMLDivElement | null = null + + public hide(): void { + if (this.div != null) { + this.div.remove() + this.div = null + } + } + + protected abstract showText(text: HTMLParagraphElement): void + + public show(): HTMLDivElement { + let callerDiv: HTMLDivElement = (this.caller != null) ? this.caller.show() : topCallDiv + + if (this.div != null) { + return this.div + } else { + this.div = document.createElement("div") + this.div.className = "tree" + callerDiv.appendChild(this.div) + let text = document.createElement("p") + this.showText(text) + this.div.appendChild(text) + return this.div + } + } +} + +class Load extends Event { + loc: string + val: string + + constructor(loc: string, val: string) { + super() + this.loc = loc + this.val = val + } + + protected showText(text: HTMLParagraphElement): void { + text.className = "load" + text.insertAdjacentText('beforeend', this.loc + " " + this.val) + } +} + +class Read extends Event { + reg: string + value: string + + constructor(reg: string, value: string) { + super() + this.reg = reg + this.value = value + } + + public showText(text: HTMLParagraphElement): void { + text.className = "read" + text.insertAdjacentText('beforeend', this.reg + " " + this.value) + } +} + +class Write extends Event { + reg: string + value: string + + constructor(reg: string, value: string) { + super() + this.reg = reg + this.value = value + } + + public showText(text: HTMLParagraphElement): void { + text.className = "write" + text.insertAdjacentText('beforeend', this.reg + " " + this.value) + } +} + +class Call { + fn: string + arg: string + ret: string + callees: (Call | Event)[] = [] + caller: Call + + private div: HTMLDivElement | null = null + + private toggle: boolean = false + private toggleImg: HTMLImageElement | null = null + + constructor(fn: string, arg: string, ret: string) { + this.fn = fn + this.arg = arg + this.ret = ret + } + + public expand() { + if (this.caller != undefined) { + this.caller.expand() + } + this.showChildren() + } + + public iter(f: (call: Call) => void): void { + f(this) + this.callees.forEach((callee) => { + if (callee instanceof Call) { callee.iter(f) } + }) + + } + + public show(): HTMLDivElement { + let callerDiv: HTMLDivElement = (this.caller != null) ? this.caller.show() : topCallDiv + + if (this.div != null) { + return this.div + } else { + this.div = document.createElement("div") + this.div.className = "tree" + callerDiv.appendChild(this.div) + let text = document.createElement("p") + text.className = "call" + if (this.callees.length > 0) { + this.toggleImg = document.createElement("img") + this.toggleImg.src = "List-add.svg" + this.toggleImg.addEventListener('click', () => { + if (this.toggle) { + this.hideChildren() + } else { + this.showChildren() + } + }) + text.appendChild(this.toggleImg) + } + this.toggle = false + let display_arg = this.arg + if (this.arg.length > max_arg_length) { + display_arg = this.arg.slice(0, max_arg_length) + } + let display_ret = this.ret + if (this.ret.length > max_arg_length) { + display_ret = this.ret.slice(0, max_arg_length) + } + + text.insertAdjacentText('beforeend', this.fn + " " + display_arg + " -> " + display_ret) + this.div.appendChild(text) + return this.div + } + } + + public hide(): void { + if (this.toggle == true) { + this.hideChildren() + } + + if (this.div != null) { + this.div.remove() + this.div = null + } + if (this.toggleImg != null) { + this.toggleImg.remove() + this.toggleImg = null + } + } + + public hideChildren(): void { + this.callees.forEach(call => { + call.hide() + }) + + if (this.toggleImg != null) { + this.toggleImg.src = "List-add.svg" + this.toggle = false + } else { + alert("this.toggleImg was null!") + } + } + + public showChildren(): void { + this.callees.forEach(call => { + call.show() + }); + + if (this.toggleImg != null) { + this.toggleImg.src = "List-remove.svg" + this.toggle = true + } else { + alert("this.toggleImg was null!") + } + } + + public appendChild(child: Call | Write | Read | Load): void { + child.caller = this + + this.callees.push(child) + } +} + +document.addEventListener('DOMContentLoaded', () => { + let rootCall = new Call("ROOT", "", "") + topCallDiv.id = "root" + document.getElementById("container")!.appendChild(topCallDiv) + + let commandInput = document.getElementById("command") as HTMLInputElement + + commandInput.addEventListener("keydown", (event) => { + if(event.keyCode == 13) { + let cmd = commandInput.value.split(" ") + commandInput.value = "" + + if (cmd[0] == "function") { + rootCall.iter((call) => { + if (call.fn == cmd[1]) { call.caller.expand() } + }) + } + } + }) + + let files = dialog.showOpenDialog(remote.getCurrentWindow(), {title: "Select log file", defaultPath: app.getAppPath()}) + + if (files == [] || files == undefined) { + dialog.showErrorBox("Error", "No file selected") + app.exit(1) + } + + fs.readFile(files[0], 'utf-8', (err, data) => { + if (err) { + dialog.showErrorBox("Error", "An error occurred when reading the log: " + err.message) + app.exit(1) + } + + let lines = data.split("\n") + // let indents = lines.map(line => line.search(/[^\s]/) / 2) + lines = lines.map(line => line.trim()) + + let stack : Call[] = [rootCall] + + lines.forEach(line => { + if (line.match(/^Call:/)) { + let words = line.slice(6).split(" ") + let call = new Call(words[0], words.slice(1).join(" "), "") + if (stack.length > 0) { + stack[stack.length - 1].appendChild(call) + } + stack.push(call) + } else if (line.match(/^Return:/)) { + let call = stack.pop() + if (call == undefined) { + alert("Unbalanced return") + app.exit(1) + } else { + call.ret = line.slice(8) + } + } else if (line.match(/^Write:/)) { + let words = line.slice(7).split(" ") + let write = new Write(words[0], words.slice(1).join(" ")) + if (stack.length > 0) { + stack[stack.length - 1].appendChild(write) + } + } else if (line.match(/^Read:/)) { + let words = line.slice(6).split(" ") + let read = new Read(words[0], words.slice(1).join(" ")) + if (stack.length > 0) { + stack[stack.length - 1].appendChild(read) + } + } else if (line.match(/^Load:/)) { + let words = line.slice(6).split(" ") + let load = new Load(words[0], words[1]) + if (stack.length > 0) { + stack[stack.length - 1].appendChild(load) + } + } + }) + + rootCall.show() + }) +}) \ No newline at end of file diff --git a/src/trace_viewer/main.ts b/src/trace_viewer/main.ts new file mode 100644 index 00000000..5cc33452 --- /dev/null +++ b/src/trace_viewer/main.ts @@ -0,0 +1,12 @@ +import {app, BrowserWindow} from 'electron' + +let win : BrowserWindow | null = null + +app.on('ready', () => { + win = new BrowserWindow({width: 1920, height: 1200}) + win.loadURL('file://' + __dirname + '/index.html') + //win.webContents.openDevTools() + win.on('close', () => { + win = null + }) +}) \ No newline at end of file diff --git a/src/trace_viewer/package.json b/src/trace_viewer/package.json new file mode 100644 index 00000000..e3a88d30 --- /dev/null +++ b/src/trace_viewer/package.json @@ -0,0 +1,15 @@ +{ + "name": "trace_viewer", + "version": "1.0.0", + "description": "", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "tsc": "./node_modules/typescript/bin/tsc" + }, + "devDependencies": { + "@types/node": "^8.0.46", + "electron": "1.7.9", + "typescript": "^2.5.3" + } +} diff --git a/src/trace_viewer/tsconfig.json b/src/trace_viewer/tsconfig.json new file mode 100644 index 00000000..e66156b3 --- /dev/null +++ b/src/trace_viewer/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "moduleResolution": "node", + "pretty": true, + "newLine": "LF", + "allowSyntheticDefaultImports": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "sourceMap": true, + "strictNullChecks": true, + "skipLibCheck": true, + "allowJs": true, + "jsx": "preserve" + } +} \ No newline at end of file -- cgit v1.2.3