Index: runtime/observatory/lib/src/elements/script_inset.dart |
diff --git a/runtime/observatory/lib/src/elements/script_inset.dart b/runtime/observatory/lib/src/elements/script_inset.dart |
index 1f57b2abeb9a4eb4bba5658204d982d620e992f9..024d1602ab4b64b9753df9c3b9be8af78fa0db19 100644 |
--- a/runtime/observatory/lib/src/elements/script_inset.dart |
+++ b/runtime/observatory/lib/src/elements/script_inset.dart |
@@ -13,53 +13,59 @@ import 'package:polymer/polymer.dart'; |
const nbsp = "\u00A0"; |
-void addInfoBox(content, infoBox) { |
- infoBox.style.position = 'absolute'; |
- infoBox.style.padding = '1em'; |
- infoBox.style.border = 'solid black 2px'; |
- infoBox.style.zIndex = '10'; |
- infoBox.style.backgroundColor = 'white'; |
- infoBox.style.cursor = 'auto'; |
- infoBox.style.display = 'none'; // Initially hidden. |
- |
+void addInfoBox(Element content, Function infoBoxGenerator) { |
+ var infoBox; |
var show = false; |
+ var originalBackground = content.style.backgroundColor; |
+ buildInfoBox() { |
+ infoBox = infoBoxGenerator(); |
+ infoBox.style.position = 'absolute'; |
+ infoBox.style.padding = '1em'; |
+ infoBox.style.border = 'solid black 2px'; |
+ infoBox.style.zIndex = '10'; |
+ infoBox.style.backgroundColor = 'white'; |
+ infoBox.style.cursor = 'auto'; |
+ content.append(infoBox); |
+ } |
content.onClick.listen((event) { |
show = !show; |
+ if (infoBox == null) buildInfoBox(); // Created lazily on the first click. |
infoBox.style.display = show ? 'block' : 'none'; |
- content.style.backgroundColor = show ? 'white' : ''; |
+ content.style.backgroundColor = show ? 'white' : originalBackground; |
}); |
// Causes infoBox to be positioned relative to the bottom-left of content. |
content.style.display = 'inline-block'; |
content.style.cursor = 'pointer'; |
- content.append(infoBox); |
} |
-abstract class Annotation { |
+abstract class Annotation implements Comparable<Annotation> { |
int line; |
int columnStart; |
int columnStop; |
void applyStyleTo(element); |
-} |
-class CurrentExecutionAnnotation extends Annotation { |
- void applyStyleTo(element) { |
- if (element == null) { |
- return; // TODO(rmacnak): Handling overlapping annotations. |
+ int compareTo(Annotation other) { |
+ if (line == other.line) { |
+ return columnStart.compareTo(other.columnStart); |
} |
- element.classes.add("currentCol"); |
- element.title = "Current execution"; |
+ return line.compareTo(other.line); |
} |
-} |
-class CallSiteAnnotation extends Annotation { |
- CallSite callSite; |
+ Element table() { |
+ var e = new DivElement(); |
+ e.style.display = "table"; |
+ e.style.color = "#333"; |
+ e.style.font = "400 14px 'Montserrat', sans-serif"; |
+ return e; |
+ } |
Element row([content]) { |
var e = new DivElement(); |
e.style.display = "table-row"; |
if (content is String) e.text = content; |
+ if (content is Element) e.children.add(content); |
return e; |
} |
@@ -77,32 +83,88 @@ class CallSiteAnnotation extends Annotation { |
e.ref = object; |
return e; |
} |
+} |
- Element entriesTable() { |
- var e = new DivElement(); |
- e.style.display = "table"; |
- e.style.color = "#333"; |
- e.style.font = "400 14px 'Montserrat', sans-serif"; |
+class CurrentExecutionAnnotation extends Annotation { |
+ void applyStyleTo(element) { |
+ if (element == null) { |
+ return; // TODO(rmacnak): Handling overlapping annotations. |
+ } |
+ element.classes.add("currentCol"); |
+ element.title = "Current execution"; |
+ } |
+} |
- if (callSite.entries.isEmpty) { |
- e.append(row('Did not execute')); |
- } else { |
- var r = row(); |
- r.append(cell("Container")); |
- r.append(cell("Count")); |
- r.append(cell("Target")); |
- e.append(r); |
+class CallSiteAnnotation extends Annotation { |
+ CallSite callSite; |
+ |
+ CallSiteAnnotation(this.callSite) { |
+ line = callSite.line; |
+ columnStart = callSite.column - 1; // Call site is 1-origin. |
+ var tokenLength = callSite.name.length; // Approximate. |
+ if (callSite.name.startsWith("get:") || |
+ callSite.name.startsWith("set:")) tokenLength -= 4; |
+ columnStop = columnStart + tokenLength; |
+ } |
+ |
+ void applyStyleTo(element) { |
+ if (element == null) { |
+ return; // TODO(rmacnak): Handling overlapping annotations. |
+ } |
+ element.style.fontWeight = "bold"; |
+ element.title = "Call site: ${callSite.name}"; |
- for (var entry in callSite.entries) { |
+ addInfoBox(element, () { |
+ var details = table(); |
+ if (callSite.entries.isEmpty) { |
+ details.append(row('Did not execute')); |
+ } else { |
var r = row(); |
- r.append(cell(serviceRef(entry.receiverContainer))); |
- r.append(cell(entry.count.toString())); |
- r.append(cell(serviceRef(entry.target))); |
- e.append(r); |
+ r.append(cell("Container")); |
+ r.append(cell("Count")); |
+ r.append(cell("Target")); |
+ details.append(r); |
+ |
+ for (var entry in callSite.entries) { |
+ var r = row(); |
+ r.append(cell(serviceRef(entry.receiverContainer))); |
+ r.append(cell(entry.count.toString())); |
+ r.append(cell(serviceRef(entry.target))); |
+ details.append(r); |
+ } |
} |
- } |
+ return details; |
+ }); |
+ } |
+} |
- return e; |
+ |
+class FunctionDeclarationAnnotation extends Annotation { |
+ ServiceFunction function; |
+ |
+ FunctionDeclarationAnnotation(this.function) { |
+ assert(function.loaded); |
+ var script = function.script; |
+ line = script.tokenToLine(function.tokenPos); |
+ columnStart = script.tokenToCol(function.tokenPos); |
+ if ((line == null) || (columnStart == null)) { |
+ line = 0; |
+ columnStart = 0; |
+ columnStop = 0; |
+ } else { |
+ columnStart--; // 1-origin -> 0-origin. |
+ |
+ // The method's token position is at the beginning of the method |
+ // declaration, which may be a return type annotation, metadata, static |
+ // modifier, etc. Try to scan forward to position this annotation on the |
+ // function's name instead. |
+ var lineSource = script.getLine(line).text; |
+ var betterStart = lineSource.indexOf(function.name, columnStart); |
+ if (betterStart != -1) { |
+ columnStart = betterStart; |
+ } |
+ columnStop = columnStart + function.name.length; |
+ } |
} |
void applyStyleTo(element) { |
@@ -110,9 +172,37 @@ class CallSiteAnnotation extends Annotation { |
return; // TODO(rmacnak): Handling overlapping annotations. |
} |
element.style.fontWeight = "bold"; |
- element.title = "Call site: ${callSite.name}"; |
+ element.title = "Function declaration: ${function.name}"; |
+ |
+ if (function.isOptimizable == false || |
+ function.isInlinable == false || |
+ function.deoptimizations >0) { |
+ element.style.backgroundColor = "red"; |
+ } |
+ |
+ addInfoBox(element, () { |
+ var details = table(); |
+ var r = row(); |
+ r.append(cell("Function")); |
+ r.append(cell(serviceRef(function))); |
+ details.append(r); |
- addInfoBox(element, entriesTable()); |
+ r = row(); |
+ r.append(cell("Usage Count")); |
+ r.append(cell("${function.usageCounter}")); |
+ details.append(r); |
+ |
+ if (function.isOptimizable == false) { |
+ details.append(row(cell("Unoptimizable!"))); |
+ } |
+ if (function.isInlinable == false) { |
+ details.append(row(cell("Not inlinable!"))); |
+ } |
+ if (function.deoptimizations > 0) { |
+ details.append(row("Deoptimized ${function.deoptimizations} times!")); |
+ } |
+ return details; |
+ }); |
} |
} |
@@ -226,6 +316,21 @@ class ScriptInsetElement extends ObservatoryElement { |
container.children.add(table); |
} |
+ void loadFunctionsOf(Library lib) { |
+ lib.load().then((lib) { |
+ for (var func in lib.functions) { |
+ func.load(); |
+ } |
+ for (var cls in lib.classes) { |
+ cls.load().then((cls) { |
+ for (var func in cls.functions) { |
+ func.load(); |
+ } |
+ }); |
+ } |
+ }); |
+ } |
+ |
void computeAnnotations() { |
startLine = (startPos != null |
? script.tokenToLine(startPos) |
@@ -249,22 +354,26 @@ class ScriptInsetElement extends ObservatoryElement { |
annotations.add(a); |
} |
+ loadFunctionsOf(script.library); |
+ |
+ for (var func in script.library.functions) { |
+ if (func.script == script) { |
+ annotations.add(new FunctionDeclarationAnnotation(func)); |
+ } |
+ } |
+ for (var cls in script.library.classes) { |
+ for (var func in cls.functions) { |
+ if (func.script == script) { |
+ annotations.add(new FunctionDeclarationAnnotation(func)); |
+ } |
+ } |
+ } |
+ |
for (var callSite in script.callSites) { |
- var a = new CallSiteAnnotation(); |
- a.line = callSite.line; |
- a.columnStart = callSite.column - 1; // Call site is 1-origin. |
- var tokenLength = callSite.name.length; // Approximate. |
- a.columnStop = a.columnStart + tokenLength; |
- a.callSite = callSite; |
- annotations.add(a); |
+ annotations.add(new CallSiteAnnotation(callSite)); |
} |
- annotations.sort((a, b) { |
- if (a.line == b.line) { |
- return a.columnStart.compareTo(b.columnStart); |
- } |
- return a.line.compareTo(b.line); |
- }); |
+ annotations.sort(); |
} |
Element linesTable() { |
@@ -322,8 +431,59 @@ class ScriptInsetElement extends ObservatoryElement { |
} |
Element lineBreakpointElement(ScriptLine line) { |
- BreakpointToggleElement e = new Element.tag("breakpoint-toggle"); |
- e.line = line; |
+ var e = new DivElement(); |
+ var busy = false; |
+ if (line == null || !line.possibleBpt) { |
+ e.classes.add("emptyBreakpoint"); |
+ e.text = nbsp; |
+ return e; |
+ } |
+ e.text = 'B'; |
+ update() { |
+ if (busy) { |
+ e.classes.clear(); |
+ e.classes.add("busyBreakpoint"); |
+ } else { |
+ if (line.breakpoints != null) { |
+ if (line.breakpointResolved) { |
+ e.classes.clear(); |
+ e.classes.add("resolvedBreakpoint"); |
+ } else { |
+ e.classes.clear(); |
+ e.classes.add("unresolvedBreakpoint"); |
+ } |
+ } else { |
+ e.classes.clear(); |
+ e.classes.add("possibleBreakpoint"); |
+ } |
+ } |
+ } |
+ line.changes.listen((_) => update()); |
+ e.onClick.listen((event) { |
+ if (busy) { |
+ return; |
+ } |
+ busy = true; |
+ if (line.breakpoints == null) { |
+ // No breakpoint. Add it. |
+ line.script.isolate.addBreakpoint(line.script, line.line).then((_) { |
+ busy = false; |
+ update(); |
+ }); |
+ } else { |
+ // Existing breakpoint. Remove it. |
+ List pending = []; |
+ for (var bpt in line.breakpoints) { |
+ pending.add(line.script.isolate.removeBreakpoint(bpt)); |
+ } |
+ Future.wait(pending).then((_) { |
+ busy = false; |
+ update(); |
+ }); |
+ } |
+ update(); |
+ }); |
+ update(); |
return e; |
} |
@@ -384,33 +544,3 @@ class ScriptInsetElement extends ObservatoryElement { |
ScriptInsetElement.created() : super.created(); |
} |
- |
-@CustomTag('breakpoint-toggle') |
-class BreakpointToggleElement extends ObservatoryElement { |
- @published ScriptLine line; |
- @observable bool busy = false; |
- |
- void toggleBreakpoint(var a, var b, var c) { |
- if (busy) { |
- return; |
- } |
- busy = true; |
- if (line.breakpoints == null) { |
- // No breakpoint. Add it. |
- line.script.isolate.addBreakpoint(line.script, line.line).then((_) { |
- busy = false; |
- }); |
- } else { |
- // Existing breakpoint. Remove it. |
- List pending = []; |
- for (var bpt in line.breakpoints) { |
- pending.add(line.script.isolate.removeBreakpoint(bpt)); |
- } |
- Future.wait(pending).then((_) { |
- busy = false; |
- }); |
- } |
- } |
- |
- BreakpointToggleElement.created() : super.created(); |
-} |