[profiler] Web UI: add summary of opts/deopts.

This adds optimization and deoptimization counts to the Web UI. Also, the function timeline
now shows optimization and deoptimization marks.

Review-Url: https://codereview.chromium.org/2753543006
Cr-Commit-Position: refs/heads/master@{#44033}
This commit is contained in:
jarin 2017-03-22 09:02:25 -07:00 committed by Commit bot
parent 2ff2a0c65d
commit 12d815b36e
15 changed files with 606 additions and 133 deletions

View File

@ -105,7 +105,9 @@ class CodeEventListener {
virtual void CodeMovingGCEvent() = 0;
virtual void CodeDisableOptEvent(AbstractCode* code,
SharedFunctionInfo* shared) = 0;
virtual void CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta) = 0;
enum DeoptKind { kSoft, kLazy, kEager };
virtual void CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta) = 0;
};
class CodeEventDispatcher {
@ -170,8 +172,9 @@ class CodeEventDispatcher {
void CodeDisableOptEvent(AbstractCode* code, SharedFunctionInfo* shared) {
CODE_EVENT_DISPATCH(CodeDisableOptEvent(code, shared));
}
void CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta) {
CODE_EVENT_DISPATCH(CodeDeoptEvent(code, pc, fp_to_sp_delta));
void CodeDeoptEvent(Code* code, CodeEventListener::DeoptKind kind, Address pc,
int fp_to_sp_delta) {
CODE_EVENT_DISPATCH(CodeDeoptEvent(code, kind, pc, fp_to_sp_delta));
}
#undef CODE_EVENT_DISPATCH

View File

@ -445,6 +445,24 @@ const char* Deoptimizer::MessageFor(BailoutType type) {
return NULL;
}
namespace {
CodeEventListener::DeoptKind DeoptKindOfBailoutType(
Deoptimizer::BailoutType bailout_type) {
switch (bailout_type) {
case Deoptimizer::EAGER:
return CodeEventListener::kEager;
case Deoptimizer::SOFT:
return CodeEventListener::kSoft;
case Deoptimizer::LAZY:
return CodeEventListener::kLazy;
}
UNREACHABLE();
return CodeEventListener::kEager;
}
} // namespace
Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction* function,
BailoutType type, unsigned bailout_id, Address from,
int fp_to_sp_delta)
@ -509,7 +527,9 @@ Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction* function,
disallow_heap_allocation_ = new DisallowHeapAllocation();
#endif // DEBUG
if (compiled_code_->kind() == Code::OPTIMIZED_FUNCTION) {
PROFILE(isolate_, CodeDeoptEvent(compiled_code_, from_, fp_to_sp_delta_));
PROFILE(isolate_,
CodeDeoptEvent(compiled_code_, DeoptKindOfBailoutType(type), from_,
fp_to_sp_delta_));
}
unsigned size = ComputeInputFrameSize();
int parameter_count =

View File

@ -843,12 +843,41 @@ void Logger::SharedLibraryEvent(const std::string& library_path,
msg.WriteToLogFile();
}
void Logger::CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta) {
if (!log_->IsEnabled() || !FLAG_log_internal_timer_events) return;
void Logger::CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta) {
if (!log_->IsEnabled()) return;
Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo(code, pc);
Log::MessageBuilder msg(log_);
int since_epoch = static_cast<int>(timer_.Elapsed().InMicroseconds());
msg.Append("code-deopt,%d,%d", since_epoch, code->CodeSize());
msg.Append("code-deopt,%d,%d,", since_epoch, code->CodeSize());
msg.AppendAddress(code->address());
// Deoptimization position.
std::ostringstream deopt_location;
int inlining_id = -1;
int script_offset = -1;
if (info.position.IsKnown()) {
info.position.Print(deopt_location, code);
inlining_id = info.position.InliningId();
script_offset = info.position.ScriptOffset();
} else {
deopt_location << "<unknown>";
}
msg.Append(",%d,%d,", inlining_id, script_offset);
switch (kind) {
case kLazy:
msg.Append("\"lazy\",");
break;
case kSoft:
msg.Append("\"soft\",");
break;
case kEager:
msg.Append("\"eager\",");
break;
}
msg.AppendDoubleQuotedString(deopt_location.str().c_str());
msg.Append(",");
msg.AppendDoubleQuotedString(DeoptimizeReasonToString(info.deopt_reason));
msg.WriteToLogFile();
}
@ -972,6 +1001,10 @@ void Logger::CallbackEventInternal(const char* prefix, Name* name,
msg.Append("%s,%s,-2,",
kLogEventsNames[CodeEventListener::CODE_CREATION_EVENT],
kLogEventsNames[CodeEventListener::CALLBACK_TAG]);
int timestamp = timer_.IsStarted()
? static_cast<int>(timer_.Elapsed().InMicroseconds())
: -1;
msg.Append("%d,", timestamp);
msg.AppendAddress(entry_point);
if (name->IsString()) {
std::unique_ptr<char[]> str =
@ -1007,23 +1040,31 @@ void Logger::SetterCallbackEvent(Name* name, Address entry_point) {
CallbackEventInternal("set ", name, entry_point);
}
static void AppendCodeCreateHeader(Log::MessageBuilder* msg,
CodeEventListener::LogEventsAndTags tag,
AbstractCode* code) {
namespace {
void AppendCodeCreateHeader(Log::MessageBuilder* msg,
CodeEventListener::LogEventsAndTags tag,
AbstractCode* code, base::ElapsedTimer* timer) {
DCHECK(msg);
msg->Append("%s,%s,%d,",
kLogEventsNames[CodeEventListener::CODE_CREATION_EVENT],
kLogEventsNames[tag], code->kind());
int timestamp = timer->IsStarted()
? static_cast<int>(timer->Elapsed().InMicroseconds())
: -1;
msg->Append("%d,", timestamp);
msg->AppendAddress(code->address());
msg->Append(",%d,", code->ExecutableSize());
}
} // namespace
void Logger::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
AbstractCode* code, const char* comment) {
if (!is_logging_code_events()) return;
if (!FLAG_log_code || !log_->IsEnabled()) return;
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, tag, code);
AppendCodeCreateHeader(&msg, tag, code, &timer_);
msg.AppendDoubleQuotedString(comment);
msg.WriteToLogFile();
}
@ -1033,7 +1074,7 @@ void Logger::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
if (!is_logging_code_events()) return;
if (!FLAG_log_code || !log_->IsEnabled()) return;
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, tag, code);
AppendCodeCreateHeader(&msg, tag, code, &timer_);
if (name->IsString()) {
msg.Append('"');
msg.AppendDetailed(String::cast(name), false);
@ -1055,7 +1096,7 @@ void Logger::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
}
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, tag, code);
AppendCodeCreateHeader(&msg, tag, code, &timer_);
if (name->IsString()) {
std::unique_ptr<char[]> str =
String::cast(name)->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
@ -1079,7 +1120,7 @@ void Logger::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
if (!is_logging_code_events()) return;
if (!FLAG_log_code || !log_->IsEnabled()) return;
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, tag, code);
AppendCodeCreateHeader(&msg, tag, code, &timer_);
std::unique_ptr<char[]> name =
shared->DebugName()->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
msg.Append("\"%s ", name.get());
@ -1101,7 +1142,7 @@ void Logger::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
if (!is_logging_code_events()) return;
if (!FLAG_log_code || !log_->IsEnabled()) return;
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, tag, code);
AppendCodeCreateHeader(&msg, tag, code, &timer_);
msg.Append("\"args_count: %d\"", args_count);
msg.WriteToLogFile();
}
@ -1130,7 +1171,7 @@ void Logger::RegExpCodeCreateEvent(AbstractCode* code, String* source) {
if (!is_logging_code_events()) return;
if (!FLAG_log_code || !log_->IsEnabled()) return;
Log::MessageBuilder msg(log_);
AppendCodeCreateHeader(&msg, CodeEventListener::REG_EXP_TAG, code);
AppendCodeCreateHeader(&msg, CodeEventListener::REG_EXP_TAG, code, &timer_);
msg.Append('"');
msg.AppendDetailed(source, false);
msg.Append('"');

View File

@ -185,7 +185,8 @@ class Logger : public CodeEventListener {
void CodeNameEvent(Address addr, int pos, const char* code_name);
void CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta);
void CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta);
void ICEvent(const char* type, bool keyed, const Address pc, int line,
int column, Map* map, Object* key, char old_state,
@ -394,7 +395,8 @@ class CodeEventLogger : public CodeEventListener {
void SetterCallbackEvent(Name* name, Address entry_point) override {}
void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void CodeMovingGCEvent() override {}
void CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta) override {}
void CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta) override {}
private:
class NameBuffer;

View File

@ -145,7 +145,7 @@ void ProfilerListener::CodeDisableOptEvent(AbstractCode* code,
DispatchCodeEvent(evt_rec);
}
void ProfilerListener::CodeDeoptEvent(Code* code, Address pc,
void ProfilerListener::CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta) {
CodeEventsContainer evt_rec(CodeEventRecord::CODE_DEOPT);
CodeDeoptEventRecord* rec = &evt_rec.CodeDeoptEventRecord_;

View File

@ -43,7 +43,8 @@ class ProfilerListener : public CodeEventListener {
void CodeMoveEvent(AbstractCode* from, Address to) override;
void CodeDisableOptEvent(AbstractCode* code,
SharedFunctionInfo* shared) override;
void CodeDeoptEvent(Code* code, Address pc, int fp_to_sp_delta) override;
void CodeDeoptEvent(Code* code, DeoptKind kind, Address pc,
int fp_to_sp_delta) override;
void GetterCallbackEvent(Name* name, Address entry_point) override;
void RegExpCodeCreateEvent(AbstractCode* code, String* source) override;
void SetterCallbackEvent(Name* name, Address entry_point) override;

View File

@ -376,7 +376,7 @@ TEST(LogCallbacks) {
ObjMethod1_entry = *FUNCTION_ENTRYPOINT_ADDRESS(ObjMethod1_entry);
#endif
i::EmbeddedVector<char, 100> ref_data;
i::SNPrintF(ref_data, "code-creation,Callback,-2,%p,1,\"method1\"",
i::SNPrintF(ref_data, "code-creation,Callback,-2,-1,%p,1,\"method1\"",
static_cast<void*>(ObjMethod1_entry));
CHECK(StrNStr(log.start(), ref_data.start(), log.length()));
@ -429,7 +429,7 @@ TEST(LogAccessorCallbacks) {
#endif
EmbeddedVector<char, 100> prop1_getter_record;
i::SNPrintF(prop1_getter_record,
"code-creation,Callback,-2,%p,1,\"get prop1\"",
"code-creation,Callback,-2,-1,%p,1,\"get prop1\"",
static_cast<void*>(Prop1Getter_entry));
CHECK(StrNStr(log.start(), prop1_getter_record.start(), log.length()));
@ -439,7 +439,7 @@ TEST(LogAccessorCallbacks) {
#endif
EmbeddedVector<char, 100> prop1_setter_record;
i::SNPrintF(prop1_setter_record,
"code-creation,Callback,-2,%p,1,\"set prop1\"",
"code-creation,Callback,-2,-1,%p,1,\"set prop1\"",
static_cast<void*>(Prop1Setter_entry));
CHECK(StrNStr(log.start(), prop1_setter_record.start(), log.length()));
@ -449,7 +449,7 @@ TEST(LogAccessorCallbacks) {
#endif
EmbeddedVector<char, 100> prop2_getter_record;
i::SNPrintF(prop2_getter_record,
"code-creation,Callback,-2,%p,1,\"get prop2\"",
"code-creation,Callback,-2,-1,%p,1,\"get prop2\"",
static_cast<void*>(Prop2Getter_entry));
CHECK(StrNStr(log.start(), prop2_getter_record.start(), log.length()));
log.Dispose();

View File

@ -78,9 +78,9 @@ ProfileTestDriver.prototype.addFunctions_ = function() {
this.profile.addLibrary('lib2', 0x21000, 0x22000);
this.profile.addStaticCode('lib2-f1', 0x21100, 0x21900);
this.profile.addStaticCode('lib2-f2', 0x21200, 0x21500);
this.profile.addCode('T', 'F1', 0x50100, 0x100);
this.profile.addCode('T', 'F2', 0x50200, 0x100);
this.profile.addCode('T', 'F3', 0x50400, 0x100);
this.profile.addCode('T', 'F1', 1, 0x50100, 0x100);
this.profile.addCode('T', 'F2', 2, 0x50200, 0x100);
this.profile.addCode('T', 'F3', 3, 0x50400, 0x100);
};

View File

@ -2,9 +2,9 @@ shared-library,"shell",0x08048000,0x081ee000,0
shared-library,"/lib32/libm-2.7.so",0xf7db6000,0xf7dd9000,0
shared-library,"ffffe000-fffff000",0xffffe000,0xfffff000,0
profiler,"begin",1
code-creation,Stub,0,0x424260,348,"CompareStub_GE"
code-creation,LazyCompile,0,0x2a8100,18535,"DrawQube 3d-cube.js:188",0xf43abcac,
code-creation,LazyCompile,0,0x480100,3908,"DrawLine 3d-cube.js:17",0xf43abc50,
code-creation,Stub,0,100,0x424260,348,"CompareStub_GE"
code-creation,LazyCompile,0,101,0x2a8100,18535,"DrawQube 3d-cube.js:188",0xf43abcac,
code-creation,LazyCompile,0,102,0x480100,3908,"DrawLine 3d-cube.js:17",0xf43abc50,
tick,0x424284,0,0,0x480600,0,0x2aaaa5
tick,0x42429f,0,0,0x480600,0,0x2aacb4
tick,0x48063d,0,0,0x2d0f7c,0,0x2aaec6

View File

@ -2,13 +2,13 @@ shared-library,"shell",0x08048000,0x081ee000,0
shared-library,"/lib32/libm-2.7.so",0xf7db6000,0xf7dd9000,0
shared-library,"ffffe000-fffff000",0xffffe000,0xfffff000,0
profiler,"begin",1
code-creation,Stub,0,0xf540a100,474,"CEntryStub"
code-creation,Script,0,0xf541cd80,736,"exp.js"
code-creation,Stub,0,0xf541d0e0,47,"RuntimeStub_Math_exp"
code-creation,LazyCompile,0,0xf541d120,145,"exp native math.js:41"
code-creation,Stub,0,100,0xf540a100,474,"CEntryStub"
code-creation,Script,0,101,0xf541cd80,736,"exp.js"
code-creation,Stub,0,102,0xf541d0e0,47,"RuntimeStub_Math_exp"
code-creation,LazyCompile,0,103,0xf541d120,145,"exp native math.js:41"
function-creation,0xf441d280,0xf541d120
code-creation,LoadIC,0,0xf541d280,117,"j"
code-creation,LoadIC,0,0xf541d360,63,"i"
code-creation,LoadIC,0,104,0xf541d280,117,"j"
code-creation,LoadIC,0,105,0xf541d360,63,"i"
tick,0x80f82d1,0,0,0,0,0xf541ce5c
tick,0x80f89a1,0,0,0,0,0xf541ce5c
tick,0x8123b5c,0,0,0,0,0xf541d1a1,0xf541ceea

View File

@ -135,7 +135,7 @@ Profile.prototype.addStaticCode = function(
* @param {number} size Code entry size.
*/
Profile.prototype.addCode = function(
type, name, start, size) {
type, name, timestamp, start, size) {
var entry = new Profile.DynamicCodeEntry(size, type, name);
this.codeMap_.addCode(start, entry);
return entry;
@ -153,7 +153,7 @@ Profile.prototype.addCode = function(
* @param {Profile.CodeState} state Optimization state.
*/
Profile.prototype.addFuncCode = function(
type, name, start, size, funcAddr, state) {
type, name, timestamp, start, size, funcAddr, state) {
// As code and functions are in the same address space,
// it is safe to put them in a single code map.
var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
@ -192,6 +192,10 @@ Profile.prototype.moveCode = function(from, to) {
}
};
Profile.prototype.deoptCode = function(
timestamp, code, inliningId, scriptOffset, bailoutType,
sourcePositionText, deoptReasonText) {
};
/**
* Reports about deletion of a dynamic code entry.
@ -864,18 +868,23 @@ JsonProfile.prototype.addStaticCode = function(
};
JsonProfile.prototype.addCode = function(
kind, name, start, size) {
kind, name, timestamp, start, size) {
var entry = new CodeMap.CodeEntry(size, name, 'CODE');
this.codeMap_.addCode(start, entry);
entry.codeId = this.codeEntries_.length;
this.codeEntries_.push({name : entry.name, type : entry.type, kind : kind});
this.codeEntries_.push({
name : entry.name,
timestamp: timestamp,
type : entry.type,
kind : kind
});
return entry;
};
JsonProfile.prototype.addFuncCode = function(
kind, name, start, size, funcAddr, state) {
kind, name, timestamp, start, size, funcAddr, state) {
// As code and functions are in the same address space,
// it is safe to put them in a single code map.
var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
@ -921,7 +930,8 @@ JsonProfile.prototype.addFuncCode = function(
name : entry.name,
type : entry.type,
kind : kind,
func : func.funcId
func : func.funcId,
tm : timestamp
});
}
return entry;
@ -935,6 +945,28 @@ JsonProfile.prototype.moveCode = function(from, to) {
}
};
JsonProfile.prototype.deoptCode = function(
timestamp, code, inliningId, scriptOffset, bailoutType,
sourcePositionText, deoptReasonText) {
let entry = this.codeMap_.findDynamicEntryByStartAddress(code);
if (entry) {
let codeId = entry.codeId;
if (!this.codeEntries_[codeId].deopt) {
// Only add the deopt if there was no deopt before.
// The subsequent deoptimizations should be lazy deopts for
// other on-stack activations.
this.codeEntries_[codeId].deopt = {
tm : timestamp,
inliningId : inliningId,
scriptOffset : scriptOffset,
posText : sourcePositionText,
reason : deoptReasonText,
bailoutType : bailoutType
};
}
}
};
JsonProfile.prototype.deleteCode = function(start) {
try {
this.codeMap_.deleteCode(start);

View File

@ -64,10 +64,10 @@ found in the LICENSE file. -->
<br>
<div id="calltree" style="display : none">
<div id="mode-bar">
</div>
<div id="mode-bar" style="display : none">
</div>
<div id="calltree" style="display : none">
<br>
Attribution:
<select id="calltree-attribution">
@ -98,6 +98,12 @@ found in the LICENSE file. -->
</table>
</div>
<div id="summary" style="display : none">
</div>
<div id="function-details" style="display : none">
</div>
<p style="font-style:italic;">
<br>
<br>

View File

@ -476,3 +476,93 @@ function generateTree(
return tickCount;
}
function computeOptimizationStats(file,
timeStart = -Infinity, timeEnd = Infinity) {
function newCollection() {
return { count : 0, functions : [], functionTable : [] };
}
function addToCollection(collection, code) {
collection.count++;
let funcData = collection.functionTable[code.func];
if (!funcData) {
funcData = { f : file.functions[code.func], instances : [] };
collection.functionTable[code.func] = funcData;
collection.functions.push(funcData);
}
funcData.instances.push(code);
}
let functionCount = 0;
let optimizedFunctionCount = 0;
let deoptimizedFunctionCount = 0;
let optimizations = newCollection();
let eagerDeoptimizations = newCollection();
let softDeoptimizations = newCollection();
let lazyDeoptimizations = newCollection();
for (let i = 0; i < file.functions.length; i++) {
let f = file.functions[i];
// Skip special SFIs that do not correspond to JS functions.
if (f.codes.length === 0) continue;
if (file.code[f.codes[0]].type !== "JS") continue;
functionCount++;
let optimized = false;
let deoptimized = false;
for (let j = 0; j < f.codes.length; j++) {
let code = file.code[f.codes[j]];
console.assert(code.type === "JS");
if (code.kind === "Opt") {
optimized = true;
if (code.tm >= timeStart && code.tm <= timeEnd) {
addToCollection(optimizations, code);
}
}
if (code.deopt) {
deoptimized = true;
if (code.deopt.tm >= timeStart && code.deopt.tm <= timeEnd) {
switch (code.deopt.bailoutType) {
case "lazy":
addToCollection(lazyDeoptimizations, code);
break;
case "eager":
addToCollection(eagerDeoptimizations, code);
break;
case "soft":
addToCollection(softDeoptimizations, code);
break;
}
}
}
}
if (optimized) {
optimizedFunctionCount++;
}
if (deoptimized) {
deoptimizedFunctionCount++;
}
}
function sortCollection(collection) {
collection.functions.sort(
(a, b) => a.instances.length - b.instances.length);
}
sortCollection(eagerDeoptimizations);
sortCollection(lazyDeoptimizations);
sortCollection(softDeoptimizations);
sortCollection(optimizations);
return {
functionCount,
optimizedFunctionCount,
deoptimizedFunctionCount,
optimizations,
eagerDeoptimizations,
lazyDeoptimizations,
softDeoptimizations,
};
}

View File

@ -14,34 +14,17 @@ function createViews() {
components.push(new CallTreeView());
components.push(new TimelineView());
components.push(new HelpView());
components.push(new SummaryView());
components.push(new ModeBarView());
let modeBar = $("mode-bar");
function addMode(id, text, active) {
let div = document.createElement("div");
div.classList = "mode-button" + (active ? " active-mode-button" : "");
div.id = "mode-" + id;
div.textContent = text;
div.onclick = () => {
if (main.currentState.callTree.mode === id) return;
let old = $("mode-" + main.currentState.callTree.mode);
old.classList = "mode-button";
div.classList = "mode-button active-mode-button";
main.setMode(id);
};
modeBar.appendChild(div);
}
addMode("bottom-up", "Bottom up", true);
addMode("top-down", "Top down");
addMode("function-list", "Functions");
main.setMode("bottom-up");
main.setMode("summary");
}
function emptyState() {
return {
file : null,
mode : "none",
currentCodeId : null,
start : 0,
end : Infinity,
timeLine : {
@ -49,7 +32,6 @@ function emptyState() {
height : 100
},
callTree : {
mode : "none",
attribution : "js-exclude-bc",
categories : "code-type",
sort : "time"
@ -68,28 +50,35 @@ let main = {
setMode(mode) {
if (mode != main.currentState.mode) {
let callTreeState = Object.assign({}, main.currentState.callTree);
callTreeState.mode = mode;
function setCallTreeModifiers(attribution, categories, sort) {
let callTreeState = Object.assign({}, main.currentState.callTree);
callTreeState.attribution = attribution;
callTreeState.categories = categories;
callTreeState.sort = sort;
return callTreeState;
}
let state = Object.assign({}, main.currentState);
switch (mode) {
case "bottom-up":
callTreeState.attribution = "js-exclude-bc";
callTreeState.categories = "code-type";
callTreeState.sort = "time";
state.callTree =
setCallTreeModifiers("js-exclude-bc", "code-type", "time");
break;
case "top-down":
callTreeState.attribution = "js-exclude-bc";
callTreeState.categories = "none";
callTreeState.sort = "time";
state.callTree =
setCallTreeModifiers("js-exclude-bc", "none", "time");
break;
case "function-list":
callTreeState.attribution = "js-exclude-bc";
callTreeState.categories = "code-type";
callTreeState.sort = "own-time";
state.callTree =
setCallTreeModifiers("js-exclude-bc", "code-type", "own-time");
break;
default:
console.error("Invalid mode");
}
main.currentState = setCallTreeState(main.currentState, callTreeState);
state.mode = mode;
main.currentState = state;
main.delayRender();
}
},
@ -201,12 +190,12 @@ let main = {
let bucketDescriptors =
[ { kinds : [ "JSOPT" ],
color : "#ffb000",
backgroundColor : "#ffe0c0",
text : "JS Optimized" },
{ kinds : [ "JSUNOPT", "BC" ],
color : "#00ff00",
backgroundColor : "#c0ffc0",
text : "JS Optimized" },
{ kinds : [ "JSUNOPT", "BC" ],
color : "#ffb000",
backgroundColor : "#ffe0c0",
text : "JS Unoptimized" },
{ kinds : [ "IC" ],
color : "#ffff00",
@ -325,6 +314,27 @@ function filterFromFilterId(id) {
}
}
function createTableExpander(indent) {
let div = document.createElement("div");
div.style.width = (indent + 0.5) + "em";
div.style.display = "inline-block";
div.style.textAlign = "right";
return div;
}
function createFunctionNode(name, codeId) {
if (codeId == -1) {
return document.createTextNode(name);
}
let nameElement = document.createElement("span");
nameElement.classList.add("codeid-link")
nameElement.onclick = function() {
main.setCurrentCode(codeId);
};
nameElement.appendChild(document.createTextNode(name));
return nameElement;
}
class CallTreeView {
constructor() {
this.element = $("calltree");
@ -377,27 +387,6 @@ class CallTreeView {
}
}
createExpander(indent) {
let div = document.createElement("div");
div.style.width = (1 + indent) + "em";
div.style.display = "inline-block";
div.style.textAlign = "right";
return div;
}
createFunctionNode(name, codeId) {
if (codeId == -1) {
return document.createTextNode(name);
}
let nameElement = document.createElement("span");
nameElement.classList.add("codeid-link")
nameElement.onclick = function() {
main.setCurrentCode(codeId);
};
nameElement.appendChild(document.createTextNode(name));
return nameElement;
}
expandTree(tree, indent) {
let that = this;
let index = 0;
@ -406,7 +395,6 @@ class CallTreeView {
let expander = tree.expander;
if (row) {
console.assert("expander");
index = row.rowIndex;
id = row.id;
@ -452,7 +440,7 @@ class CallTreeView {
c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%";
c.style.textAlign = "right";
// Exclusive time % cell.
if (this.currentState.callTree.mode !== "bottom-up") {
if (this.currentState.mode !== "bottom-up") {
c = row.insertCell(-1);
c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%";
c.style.textAlign = "right";
@ -460,16 +448,16 @@ class CallTreeView {
// Create the name cell.
let nameCell = row.insertCell();
let expander = this.createExpander(indent);
let expander = createTableExpander(indent + 1);
nameCell.appendChild(expander);
nameCell.appendChild(createTypeDiv(node.type));
nameCell.appendChild(this.createFunctionNode(node.name, node.codeId));
nameCell.appendChild(createFunctionNode(node.name, node.codeId));
// Inclusive ticks cell.
c = row.insertCell();
c.textContent = node.ticks;
c.style.textAlign = "right";
if (this.currentState.callTree.mode !== "bottom-up") {
if (this.currentState.mode !== "bottom-up") {
// Exclusive ticks cell.
c = row.insertCell(-1);
c.textContent = node.ownTicks;
@ -500,7 +488,7 @@ class CallTreeView {
expander.onclick = expandHandler;
}
fillSelects(calltree) {
fillSelects(mode, calltree) {
function addOptions(e, values, current) {
while (e.options.length > 0) {
e.remove(0);
@ -523,7 +511,7 @@ class CallTreeView {
text : "Attribute non-functions to JS functions" }
];
switch (calltree.mode) {
switch (mode) {
case "bottom-up":
addOptions(this.selectAttribution, attributions, calltree.attribution);
addOptions(this.selectCategories, [
@ -564,10 +552,22 @@ class CallTreeView {
console.error("Unexpected mode");
}
static isCallTreeMode(mode) {
switch (mode) {
case "bottom-up":
case "top-down":
case "function-list":
return true;
default:
return false;
}
}
render(newState) {
let oldState = this.currentState;
if (!newState.file) {
if (!newState.file || !CallTreeView.isCallTreeMode(newState.mode)) {
this.element.style.display = "none";
this.currentState = null;
return;
}
@ -576,7 +576,7 @@ class CallTreeView {
if (newState.file === oldState.file &&
newState.start === oldState.start &&
newState.end === oldState.end &&
newState.callTree.mode === oldState.callTree.mode &&
newState.mode === oldState.mode &&
newState.callTree.attribution === oldState.callTree.attribution &&
newState.callTree.categories === oldState.callTree.categories &&
newState.callTree.sort === oldState.callTree.sort) {
@ -587,12 +587,12 @@ class CallTreeView {
this.element.style.display = "inherit";
let mode = this.currentState.callTree.mode;
if (!oldState || mode !== oldState.callTree.mode) {
let mode = this.currentState.mode;
if (!oldState || mode !== oldState.mode) {
// Technically, we should also call this if attribution, categories or
// sort change, but the selection is already highlighted by the combobox
// itself, so we do need to do anything here.
this.fillSelects(newState.callTree);
this.fillSelects(newState.mode, newState.callTree);
}
let ownTimeClass = (mode === "bottom-up") ? "numeric-hidden" : "numeric";
@ -661,7 +661,8 @@ class TimelineView {
this.fontSize = 12;
this.imageOffset = Math.round(this.fontSize * 1.2);
this.functionTimelineHeight = 16;
this.functionTimelineHeight = 24;
this.functionTimelineTickHeight = 16;
this.currentState = null;
}
@ -864,8 +865,8 @@ class TimelineView {
bucketsGraph.push(bucketData);
}
// Draw the graph into the buffer.
let bucketWidth = width / (bucketsGraph.length - 1);
// Draw the category graph into the buffer.
let bucketWidth = width / bucketsGraph.length;
let ctx = buffer.getContext('2d');
for (let i = 0; i < bucketsGraph.length - 1; i++) {
let bucketData = bucketsGraph[i];
@ -883,25 +884,30 @@ class TimelineView {
ctx.fill();
}
}
// Draw the function ticks.
let functionTimelineYOffset = graphHeight;
let functionTimelineHeight = this.functionTimelineHeight;
let functionTimelineHalfHeight = Math.round(functionTimelineHeight / 2);
let functionTimelineTickHeight = this.functionTimelineTickHeight;
let functionTimelineHalfHeight =
Math.round(functionTimelineTickHeight / 2);
let timestampScaler = width / (lastTime - firstTime);
let timestampToX = (t) => Math.round((t - firstTime) * timestampScaler);
ctx.fillStyle = "white";
ctx.fillRect(
0,
functionTimelineYOffset,
buffer.width,
functionTimelineHeight);
this.functionTimelineHeight);
for (let i = 0; i < codeIdProcessor.blocks.length; i++) {
let block = codeIdProcessor.blocks[i];
let bucket = kindToBucketDescriptor[block.kind];
ctx.fillStyle = bucket.color;
ctx.fillRect(
Math.round((block.start - firstTime) * timestampScaler),
timestampToX(block.start),
functionTimelineYOffset,
Math.max(1, Math.round((block.end - block.start) * timestampScaler)),
block.topOfStack ? functionTimelineHeight : functionTimelineHalfHeight);
block.topOfStack ?
functionTimelineTickHeight : functionTimelineHalfHeight);
}
ctx.strokeStyle = "black";
ctx.lineWidth = "1";
@ -917,6 +923,53 @@ class TimelineView {
functionTimelineYOffset + functionTimelineHalfHeight - 0.5);
ctx.stroke();
// Draw marks for optimizations and deoptimizations in the function
// timeline.
if (currentCodeId && currentCodeId >= 0 &&
file.code[currentCodeId].func) {
let y = Math.round(functionTimelineYOffset + functionTimelineTickHeight +
(this.functionTimelineHeight - functionTimelineTickHeight) / 2);
let func = file.functions[file.code[currentCodeId].func];
for (let i = 0; i < func.codes.length; i++) {
let code = file.code[func.codes[i]];
if (code.kind === "Opt") {
if (code.deopt) {
// Draw deoptimization mark.
let x = timestampToX(code.deopt.tm);
ctx.lineWidth = 0.7;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(x - 3, y - 3);
ctx.lineTo(x + 3, y + 3);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x - 3, y + 3);
ctx.lineTo(x + 3, y - 3);
ctx.stroke();
}
// Draw optimization mark.
let x = timestampToX(code.tm);
ctx.lineWidth = 0.7;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(x - 3, y - 3);
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x - 3, y + 3);
ctx.lineTo(x, y);
ctx.stroke();
} else {
// Draw code creation mark.
let x = Math.round(timestampToX(code.tm));
ctx.beginPath();
ctx.fillStyle = "black";
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fill();
}
}
}
// Remember stuff for later.
this.buffer = buffer;
@ -958,6 +1011,218 @@ class TimelineView {
}
}
class ModeBarView {
constructor() {
let modeBar = this.element = $("mode-bar");
function addMode(id, text, active) {
let div = document.createElement("div");
div.classList = "mode-button" + (active ? " active-mode-button" : "");
div.id = "mode-" + id;
div.textContent = text;
div.onclick = () => {
if (main.currentState.mode === id) return;
let old = $("mode-" + main.currentState.mode);
old.classList = "mode-button";
div.classList = "mode-button active-mode-button";
main.setMode(id);
};
modeBar.appendChild(div);
}
addMode("summary", "Summary", true);
addMode("bottom-up", "Bottom up");
addMode("top-down", "Top down");
addMode("function-list", "Functions");
}
render(newState) {
if (!newState.file) {
this.element.style.display = "none";
return;
}
this.element.style.display = "inherit";
}
}
class SummaryView {
constructor() {
this.element = $("summary");
this.currentState = null;
}
render(newState) {
let oldState = this.currentState;
if (!newState.file || newState.mode !== "summary") {
this.element.style.display = "none";
this.currentState = null;
return;
}
this.currentState = newState;
if (oldState) {
if (newState.file === oldState.file &&
newState.start === oldState.start &&
newState.end === oldState.end) {
// No change, nothing to do.
return;
}
}
this.element.style.display = "inherit";
while (this.element.firstChild) {
this.element.removeChild(this.element.firstChild);
}
let stats = computeOptimizationStats(
this.currentState.file, newState.start, newState.end);
let table = document.createElement("table");
let rows = document.createElement("tbody");
function addRow(text, number, indent) {
let row = rows.insertRow(-1);
let textCell = row.insertCell(-1);
textCell.textContent = text;
let numberCell = row.insertCell(-1);
numberCell.textContent = number;
if (indent) {
textCell.style.textIndent = indent + "em";
numberCell.style.textIndent = indent + "em";
}
return row;
}
function makeCollapsible(row, expander) {
expander.textContent = "\u25BE";
let expandHandler = expander.onclick;
expander.onclick = () => {
let id = row.id;
let index = row.rowIndex + 1;
while (index < rows.rows.length &&
rows.rows[index].id.startsWith(id)) {
rows.deleteRow(index);
}
expander.textContent = "\u25B8";
expander.onclick = expandHandler;
}
}
function expandDeoptInstances(row, expander, instances, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < instances.length; i++) {
let childRow = rows.insertRow(index + 1);
childRow.id = row.id + i + "/";
let deopt = instances[i].deopt;
let textCell = childRow.insertCell(-1);
textCell.appendChild(document.createTextNode(deopt.posText));
textCell.style.textIndent = indent + "em";
let reasonCell = childRow.insertCell(-1);
reasonCell.appendChild(
document.createTextNode("Reason: " + deopt.reason));
reasonCell.style.textIndent = indent + "em";
}
makeCollapsible(row, expander);
}
function expandDeoptFunctionList(row, expander, list, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < list.length; i++) {
let childRow = rows.insertRow(index + 1);
childRow.id = row.id + i + "/";
let textCell = childRow.insertCell(-1);
let expander = createTableExpander(indent);
textCell.appendChild(expander);
textCell.appendChild(
createFunctionNode(list[i].f.name, list[i].f.codes[0]));
let numberCell = childRow.insertCell(-1);
numberCell.textContent = list[i].instances.length;
numberCell.style.textIndent = indent + "em";
expander.textContent = "\u25B8";
expander.onclick = () => {
expandDeoptInstances(
childRow, expander, list[i].instances, indent + 1);
};
}
makeCollapsible(row, expander);
}
function expandOptimizedFunctionList(row, expander, list, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < list.length; i++) {
let childRow = rows.insertRow(index + 1);
childRow.id = row.id + i + "/";
let textCell = childRow.insertCell(-1);
textCell.appendChild(
createFunctionNode(list[i].f.name, list[i].f.codes[0]));
textCell.style.textIndent = indent + "em";
let numberCell = childRow.insertCell(-1);
numberCell.textContent = list[i].instances.length;
numberCell.style.textIndent = indent + "em";
}
makeCollapsible(row, expander);
}
function addExpandableRow(text, list, indent, kind) {
let row = rows.insertRow(-1);
row.id = "opt-table/" + kind + "/";
let textCell = row.insertCell(-1);
let expander = createTableExpander(indent);
textCell.appendChild(expander);
textCell.appendChild(document.createTextNode(text));
let numberCell = row.insertCell(-1);
numberCell.textContent = list.count;
if (indent) {
numberCell.style.textIndent = indent + "em";
}
if (list.count > 0) {
expander.textContent = "\u25B8";
if (kind === "opt") {
expander.onclick = () => {
expandOptimizedFunctionList(
row, expander, list.functions, indent + 1, kind);
};
} else {
expander.onclick = () => {
expandDeoptFunctionList(
row, expander, list.functions, indent + 1, kind);
};
}
}
return row;
}
addRow("Total function count:", stats.functionCount);
addRow("Optimized function count:", stats.optimizedFunctionCount, 1);
addRow("Deoptimized function count:", stats.deoptimizedFunctionCount, 2);
addExpandableRow("Optimization count:", stats.optimizations, 0, "opt");
let deoptCount = stats.eagerDeoptimizations.count +
stats.softDeoptimizations.count + stats.lazyDeoptimizations.count;
addRow("Deoptimization count:", deoptCount);
addExpandableRow("Eager:", stats.eagerDeoptimizations, 1, "eager");
addExpandableRow("Lazy:", stats.lazyDeoptimizations, 1, "lazy");
addExpandableRow("Soft:", stats.softDeoptimizations, 1, "soft");
table.appendChild(rows);
this.element.appendChild(table);
}
}
class HelpView {
constructor() {
this.element = $("help");

View File

@ -88,9 +88,14 @@ function TickProcessor(
'shared-library': { parsers: [null, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary },
'code-creation': {
parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'],
parsers: [null, parseInt, parseInt, parseInt, parseInt,
null, 'var-args'],
processor: this.processCodeCreation },
'code-move': { parsers: [parseInt, parseInt],
'code-deopt': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
null, null, null],
processor: this.processCodeDeopt },
'code-move': { parsers: [parseInt, parseInt, ],
processor: this.processCodeMove },
'code-delete': { parsers: [parseInt],
processor: this.processCodeDelete },
@ -266,18 +271,26 @@ TickProcessor.prototype.processSharedLibrary = function(
TickProcessor.prototype.processCodeCreation = function(
type, kind, start, size, name, maybe_func) {
type, kind, timestamp, start, size, name, maybe_func) {
name = this.deserializedEntriesNames_[start] || name;
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
this.profile_.addFuncCode(type, name, start, size, funcAddr, state);
this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
} else {
this.profile_.addCode(type, name, start, size);
this.profile_.addCode(type, name, timestamp, start, size);
}
};
TickProcessor.prototype.processCodeDeopt = function(
timestamp, size, code, inliningId, scriptOffset, bailoutType,
sourcePositionText, deoptReasonText) {
this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset,
bailoutType, sourcePositionText, deoptReasonText);
};
TickProcessor.prototype.processCodeMove = function(from, to) {
this.profile_.moveCode(from, to);
};