[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:
parent
2ff2a0c65d
commit
12d815b36e
@ -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
|
||||
|
||||
|
@ -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 =
|
||||
|
65
src/log.cc
65
src/log.cc
@ -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,
|
||||
namespace {
|
||||
|
||||
void AppendCodeCreateHeader(Log::MessageBuilder* msg,
|
||||
CodeEventListener::LogEventsAndTags tag,
|
||||
AbstractCode* code) {
|
||||
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('"');
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -64,10 +64,10 @@ found in the LICENSE file. -->
|
||||
|
||||
<br>
|
||||
|
||||
<div id="calltree" style="display : none">
|
||||
<div id="mode-bar">
|
||||
<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>
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
function setCallTreeModifiers(attribution, categories, sort) {
|
||||
let callTreeState = Object.assign({}, main.currentState.callTree);
|
||||
callTreeState.mode = mode;
|
||||
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");
|
||||
|
@ -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);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user