Skip to content

Commit

Permalink
Fix Node build, progress bar, add Ace editor annotations + stack fram…
Browse files Browse the repository at this point in the history
…e selection, better text wrapping.
  • Loading branch information
John Vilk committed Feb 11, 2018
1 parent e14b20b commit ab4a212
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 32 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ yarn test

1. **Build** BLeak (see above).
1. **Write** a *configuration file* for your web application (see below).
2. **Run** `node build/src/frontends/bleak.js --config path/to/config.js --out path/to/where/you/want/output`
2. **Run** `node build/node/src/frontends/bleak.js --config path/to/config.js --out path/to/where/you/want/output`
* The output directory should be unique for this specific run of BLeak, otherwise it will overwrite files in the directory. It will be created if needed.
3. **Wait.** BLeak typically runs in <10 minutes, but its speed depends on the number of states in your loop and the speed of your web application.
4. **Examine** `path/to/where/you/want/output/leaks.log`, which contains a list of growing objects and stack traces responsible for growing it.
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="node_modules/d3/build/d3.min.js"></script>
<script src="build/viewer.js" type="text/javascript"></script>
<script src="build/browser/viewer.js" type="text/javascript"></script>
</body>
</html>
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"description": "Automatically finds memory leaks in single page web applications.",
"main": "dist/index.js",
"scripts": {
"build": "npm-run-all -s build:core rollup:viewer",
"build:core": "tsc",
"build": "npm-run-all -p build:node build:viewer -s rollup:viewer",
"build:node": "tsc -p tsconfig.node.json",
"build:viewer": "tsc",
"rollup:viewer": "rollup -c",
"test": "npm-run-all -s build nyc:test",
"nyc:test": "nyc mocha --require source-map-support/register build/test",
"watch": "npm-run-all -s build:core -p tsc:watch rollup:viewer:watch",
"nyc:test": "nyc mocha --require source-map-support/register build/node/test",
"watch": "npm-run-all -p build:node build:viewer -p tsc:viewer:watch tsc:watch rollup:viewer:watch",
"tsc:watch": "tsc -w",
"tsc:viewer:watch": "tsc -w -p tsconfig.node.json",
"rollup:viewer:watch": "rollup -w -c"
},
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import replace from 'rollup-plugin-replace';
// import globals from 'rollup-plugin-node-globals';
import {join} from 'path';

const inBase = join(__dirname, 'build', 'src');
const outBase = join(__dirname, 'build');
const inBase = join(__dirname, 'build', 'browser', 'src');
const outBase = join(__dirname, 'build', 'browser');

export default {
input: join(inBase, 'viewer', 'index.js'),
Expand Down
8 changes: 5 additions & 3 deletions src/lib/bleak.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,18 @@ export class BLeakDetector {
*/
public async findAndDiagnoseLeaks(): Promise<BLeakResults> {
const steps = this._numberOfSteps(true, true);
return this.diagnoseLeaks(await this.findLeakPaths(steps), true, steps);
return this.diagnoseLeaks(await this.findLeakPaths(steps), true, true);
}

/**
* Given a set of leak roots (accessible from multiple paths), runs the webpage in an
* instrumented state that collects stack traces as the objects at the roots grow.
* @param leakRoots
*/
public async diagnoseLeaks(leakRoots: LeakRoot[], loggedIn: boolean = true, steps = this._numberOfSteps(false, true)): Promise<BLeakResults> {
this._progressBar.setOperationCount(steps);
public async diagnoseLeaks(leakRoots: LeakRoot[], loggedIn: boolean = true, progressBarInitialized: boolean): Promise<BLeakResults> {
if (!progressBarInitialized) {
this._progressBar.setOperationCount(this._numberOfSteps(false, true));
}
const results = new BLeakResults(leakRoots, undefined, undefined, this._heapSnapshotSizeStats);
this._heapSnapshotSizeStats = [];
const leaksDebug = JSON.stringify(toPathTree(leakRoots));
Expand Down
11 changes: 8 additions & 3 deletions src/lib/path_to_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ function propertyAccessString(s: string | number) {
}

function prettyPrintDOMPath(): void {
const segment = PS.pop();
while (PS.nonempty()) {
if (segment.indexOrName === "root") {
const segment = PS.pop();
const name = segment.indexOrName;
if (name === "root") {
// Ignore this BLeak-inserted edge.
// We're transitioning to a path outside of the DOM, on the DOM object itself.
prettyPrintNonDOMPath();
} else if (name === 'childNodes') {
PS.print(propertyAccessString(name));
} else {
// $$$CHILD$$$n => n
const idx = parseInt((name as string).slice(11), 10);
// Should alternate between 'childNode' and indices until it gets to 'root'.
PS.print(propertyAccessString(segment.indexOrName));
PS.print(propertyAccessString(idx));
}
}
}
Expand Down
52 changes: 36 additions & 16 deletions src/viewer/components/source_code_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {default as AceEditor, Marker as AceMarker, Annotation as AceAnnotation}
import StackTraceManager from '../model/stack_trace_manager';
import BLeakResults from '../../lib/bleak_results';
import {FileLocation} from '../model/interfaces';
import {IStackFrame} from '../../common/interfaces';
import pathToString from '../../lib/path_to_string';
import 'brace/mode/javascript';
import 'brace/theme/github';
import 'brace/ext/searchbox';
Expand All @@ -21,35 +23,61 @@ interface SourceCodeViewState {
stackTraces: StackTraceManager;
// URL => (line,column)
fileState: {[url: string]: [number, number]};
// Active annotations
highlightedFrames: IStackFrame[];
}

export default class SourceCodeView extends React.Component<SourceCodeViewProps, SourceCodeViewState> {
constructor(props: SourceCodeViewProps, context?: any) {
super(props, context);
const stm = StackTraceManager.FromBLeakResults(props.results);
this.state = {
openFile: this.props.fileLocation.url,
stackTraces: StackTraceManager.FromBLeakResults(props.results),
fileState: {}
stackTraces: stm,
fileState: {},
highlightedFrames: stm.getFramesForFile(this.props.fileLocation.url)
};
this.state.fileState[this.state.openFile] = [props.fileLocation.line, props.fileLocation.column];
}

public componentDidMount() {
this._scrollAceEditor();
this._updateAceEditor();
// TODO: On click annotation / marker, select frames in left pane.
/*const editor: AceAjax.Editor = (this.refs.aceEditor as any).editor;
editor.on('click', (e) => {
const pos = e.getDocumentPosition();
const row = pos.row;
const col = pos.column;
});*/
// guttermousedown
}

public componentDidUpdate() {
this._scrollAceEditor();
this._updateAceEditor();
}

private _scrollAceEditor() {
private _updateAceEditor() {
const editor: AceAjax.Editor = (this.refs.aceEditor as any).editor;

// Scroll into view
let editorState = this.state.fileState[this.state.openFile];
if (!editorState) {
editorState = [1, 1];
this.state.fileState[this.state.openFile] = editorState;
}
(editor.renderer.scrollCursorIntoView as any)({ row: editorState[0] - 1, column: editorState[1] - 1 }, 0.5);

// Display annotations for file.
const annotations = this.state.highlightedFrames.map((f): AceAnnotation => {
const leaks = this.state.stackTraces.getLeaksForLocation(f[0], f[1], f[2]);
return {
row: f[1] - 1,
column: f[2] - 1,
type: 'error',
text: `Contributes to memory leaks:\n${leaks.map((l) => pathToString(l.paths[0])).join(",\n")}`
};
});
editor.getSession().setAnnotations(annotations);
}

public componentWillReceiveProps(props: SourceCodeViewProps) {
Expand All @@ -68,11 +96,12 @@ export default class SourceCodeView extends React.Component<SourceCodeViewProps,
const newFileState: {[url: string]: [number, number]} = Object.assign({}, this.state.fileState);
newFileState[this.state.openFile] = [middle, 1];
newFileState[url] = position;
this.setState({ openFile: url, fileState: newFileState });
const frames = this.state.stackTraces.getFramesForFile(url);
this.setState({ openFile: url, fileState: newFileState, highlightedFrames: frames });
}

public render() {
const frames = this.state.stackTraces.getFramesForFile(this.state.openFile);
const frames = this.state.highlightedFrames;
const markers = frames.map((f): AceMarker => {
// Note: Ace uses 0-index rows and cols internally.
return {
Expand All @@ -84,14 +113,6 @@ export default class SourceCodeView extends React.Component<SourceCodeViewProps,
type: 'sometype'
};
});
const annotations = frames.map((f): AceAnnotation => {
return {
row: f[1] - 1,
column: 0,
type: 'error',
text: 'Code on this line is part of a memory leak.'
};
});
return <div className="row">
<div className="col-lg-3">
<FileList files={this.props.files} editorFile={this.state.openFile} onFileSelected={(f) => {
Expand All @@ -107,7 +128,6 @@ export default class SourceCodeView extends React.Component<SourceCodeViewProps,
width="100%"
highlightActiveLine={false}
setOptions={ { highlightGutterLine: false, useWorker: false } }
annotations={annotations}
markers={markers}
value={this.props.files.getSourceFile(this.state.openFile).source} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/viewer/components/stack_frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function StackFrame(p: StackFrameComponentProps) {
const url = formatUrl(f[0]);
const line = f[1];
const col = f[2];
const selected = location.url === url && location.line === line && location.column === col;
const selected = location.url === f[0] && location.line === line && location.column === col;
return <button type="button" className={"list-group-item list-group-item-action" + (selected ? " selected" : "")} onClick={p.onStackFrameSelect.bind(null, p.frame)}>
<span className="stack-frame"><span>{functionName}</span> <span>{url}:{line}:{col}</span></span>
</button>;
Expand Down
10 changes: 10 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ body {
padding: 0;
}

/* Makes sure long leak root names wrap appropriately, otherwise the button expands to accomodate them. */
.leak-root-list .card-header button {
max-width: 100%;
}

.leak-root-list .btn-link {
white-space: normal;
text-align: left;
Expand All @@ -74,6 +79,7 @@ body {

.stack-trace .selected {
background-color: grey;
color: white;
}

.stack-frame {
Expand All @@ -84,6 +90,10 @@ body {
.stack-frame span:nth-child(2) {
flex-grow: 1;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin-left: 10px;
}

.tree-view .folder, .tree-view .file {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"dom",
"es2015"
],
"outDir": "build"
"outDir": "build/browser"
}
}
21 changes: 21 additions & 0 deletions tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"moduleResolution": "node",
"noImplicitAny": true,
"inlineSourceMap": true,
"inlineSources": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"importHelpers": true,
"noEmitHelpers": true,
"jsx": "react",
"lib": [
"dom",
"es2015"
],
"outDir": "build/node"
}
}

0 comments on commit ab4a212

Please sign in to comment.