mirror of
https://github.com/dart-lang/sdk
synced 2024-09-04 16:03:44 +00:00
[vm] Adds an API call to dump the CPU profile to the timeline
Used in https://fuchsia-review.googlesource.com/c/topaz/+/258655 Change-Id: I79b102e0d318d7be3bae44fb88f81bb8086a10d4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/94400 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Zach Anderson <zra@google.com>
This commit is contained in:
parent
ccf756dae5
commit
8332fb0631
|
@ -966,6 +966,20 @@ DART_EXPORT void Dart_NotifyIdle(int64_t deadline);
|
|||
*/
|
||||
DART_EXPORT void Dart_NotifyLowMemory();
|
||||
|
||||
/**
|
||||
* Starts the CPU sampling profiler.
|
||||
*/
|
||||
DART_EXPORT void Dart_StartProfiling();
|
||||
|
||||
/**
|
||||
* Stops the CPU sampling profiler.
|
||||
*
|
||||
* Note that some profile samples might still be taken after this fucntion
|
||||
* returns due to the asynchronous nature of the implementation on some
|
||||
* platforms.
|
||||
*/
|
||||
DART_EXPORT void Dart_StopProfiling();
|
||||
|
||||
/**
|
||||
* Notifies the VM that the current thread should not be profiled until a
|
||||
* matching call to Dart_ThreadEnableProfiling is made.
|
||||
|
@ -3164,6 +3178,20 @@ DART_EXPORT bool Dart_IsServiceIsolate(Dart_Isolate isolate);
|
|||
*/
|
||||
DART_EXPORT Dart_Port Dart_ServiceWaitForLoadPort();
|
||||
|
||||
/**
|
||||
* Writes the CPU profile to the timeline as a series of 'instant' events.
|
||||
*
|
||||
* Note that this is an expensive operation.
|
||||
*
|
||||
* \param main_port The main port of the Isolate whose profile samples to write.
|
||||
* \param error An optional error, must be free()ed by caller.
|
||||
*
|
||||
* \return Returns true if the profile is successfully written and false
|
||||
* otherwise.
|
||||
*/
|
||||
DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port,
|
||||
char** error);
|
||||
|
||||
/*
|
||||
* ====================
|
||||
* Compilation Feedback
|
||||
|
|
|
@ -122,7 +122,6 @@ DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message);
|
|||
* data references from the message are allocated by the caller and
|
||||
* will be reclaimed when returning to it.
|
||||
*/
|
||||
|
||||
typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id,
|
||||
Dart_CObject* message);
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "vm/os_thread.h"
|
||||
#include "vm/port.h"
|
||||
#include "vm/profiler.h"
|
||||
#include "vm/profiler_service.h"
|
||||
#include "vm/program_visitor.h"
|
||||
#include "vm/resolver.h"
|
||||
#include "vm/reusable_handles.h"
|
||||
|
@ -1279,6 +1280,24 @@ DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate) {
|
|||
T->EnterSafepoint();
|
||||
}
|
||||
|
||||
DART_EXPORT void Dart_StartProfiling() {
|
||||
#if !defined(PRODUCT)
|
||||
if (!FLAG_profiler) {
|
||||
FLAG_profiler = true;
|
||||
Profiler::Init();
|
||||
}
|
||||
#endif // !defined(PRODUCT)
|
||||
}
|
||||
|
||||
DART_EXPORT void Dart_StopProfiling() {
|
||||
#if !defined(PRODUCT)
|
||||
if (FLAG_profiler) {
|
||||
Profiler::Cleanup();
|
||||
FLAG_profiler = false;
|
||||
}
|
||||
#endif // !defined(PRODUCT)
|
||||
}
|
||||
|
||||
DART_EXPORT void Dart_ThreadDisableProfiling() {
|
||||
OSThread* os_thread = OSThread::Current();
|
||||
if (os_thread == NULL) {
|
||||
|
@ -1295,6 +1314,39 @@ DART_EXPORT void Dart_ThreadEnableProfiling() {
|
|||
os_thread->EnableThreadInterrupts();
|
||||
}
|
||||
|
||||
DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port,
|
||||
char** error) {
|
||||
#if defined(PRODUCT)
|
||||
return false;
|
||||
#else
|
||||
if (!FLAG_profiler) {
|
||||
if (error != NULL) {
|
||||
*error = strdup("The profiler is not running.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const intptr_t kBufferLength = 512;
|
||||
char method[kBufferLength];
|
||||
intptr_t method_length = snprintf(method, kBufferLength, "{"
|
||||
"\"jsonrpc\": \"2.0\","
|
||||
"\"method\": \"_writeCpuProfileTimeline\","
|
||||
"\"id\": \"\","
|
||||
"\"params\": {\"isolateId\": \"isolates/%" Pd64 "\"}"
|
||||
"}", main_port);
|
||||
ASSERT(method_length <= kBufferLength);
|
||||
|
||||
char* response = NULL;
|
||||
intptr_t response_length;
|
||||
bool success = Dart_InvokeVMServiceMethod(
|
||||
reinterpret_cast<uint8_t*>(method), method_length,
|
||||
reinterpret_cast<uint8_t**>(&response), &response_length,
|
||||
error);
|
||||
free(response);
|
||||
return success;
|
||||
#endif
|
||||
}
|
||||
|
||||
DART_EXPORT bool Dart_ShouldPauseOnStart() {
|
||||
#if defined(PRODUCT)
|
||||
return false;
|
||||
|
|
|
@ -732,6 +732,11 @@ class ProcessedSample : public ZoneAllocated {
|
|||
timeline_trie_ = trie;
|
||||
}
|
||||
|
||||
ProfileTrieNode* timeline_code_trie() const { return timeline_code_trie_; }
|
||||
void set_timeline_code_trie(ProfileTrieNode* trie) {
|
||||
timeline_code_trie_ = trie;
|
||||
}
|
||||
|
||||
private:
|
||||
void FixupCaller(const CodeLookupTable& clt,
|
||||
uword pc_marker,
|
||||
|
@ -753,6 +758,7 @@ class ProcessedSample : public ZoneAllocated {
|
|||
uword native_allocation_address_;
|
||||
uintptr_t native_allocation_size_bytes_;
|
||||
ProfileTrieNode* timeline_trie_;
|
||||
ProfileTrieNode* timeline_code_trie_;
|
||||
|
||||
friend class SampleBuffer;
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessedSample);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "vm/profiler_service.h"
|
||||
|
||||
#include "platform/text_buffer.h"
|
||||
#include "vm/growable_array.h"
|
||||
#include "vm/hash_map.h"
|
||||
#include "vm/log.h"
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include "vm/profiler.h"
|
||||
#include "vm/reusable_handles.h"
|
||||
#include "vm/scope_timer.h"
|
||||
#include "vm/timeline.h"
|
||||
|
||||
namespace dart {
|
||||
|
||||
|
@ -857,6 +859,7 @@ ProfileTrieNode::ProfileTrieNode(intptr_t table_index)
|
|||
exclusive_allocations_(0),
|
||||
inclusive_allocations_(0),
|
||||
children_(0),
|
||||
parent_(NULL),
|
||||
frame_id_(-1) {
|
||||
ASSERT(table_index_ >= 0);
|
||||
}
|
||||
|
@ -909,6 +912,10 @@ class ProfileCodeTrieNode : public ProfileTrieNode {
|
|||
}
|
||||
}
|
||||
|
||||
const char* ToCString(Profile* profile) const {
|
||||
return profile->GetCode(table_index())->name();
|
||||
}
|
||||
|
||||
ProfileCodeTrieNode* GetChild(intptr_t child_table_index) {
|
||||
const intptr_t length = NumChildren();
|
||||
intptr_t i = 0;
|
||||
|
@ -983,6 +990,11 @@ class ProfileFunctionTrieNode : public ProfileTrieNode {
|
|||
}
|
||||
}
|
||||
|
||||
const char* ToCString(Profile* profile) const {
|
||||
ProfileFunction* f = profile->GetFunction(table_index());
|
||||
return f->Name();
|
||||
}
|
||||
|
||||
ProfileFunctionTrieNode* GetChild(intptr_t child_table_index) {
|
||||
const intptr_t length = NumChildren();
|
||||
intptr_t i = 0;
|
||||
|
@ -1443,6 +1455,8 @@ class ProfileBuilder : public ValueObject {
|
|||
if (!sample->first_frame_executing()) {
|
||||
current = AppendExitFrame(sample->vm_tag(), current, sample);
|
||||
}
|
||||
|
||||
sample->set_timeline_code_trie(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1487,6 +1501,8 @@ class ProfileBuilder : public ValueObject {
|
|||
if (sample->truncated()) {
|
||||
current = AppendTruncatedTag(current, sample);
|
||||
}
|
||||
|
||||
sample->set_timeline_code_trie(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2462,6 +2478,50 @@ void Profile::PrintTimelineJSON(JSONStream* stream) {
|
|||
}
|
||||
}
|
||||
|
||||
void Profile::AddParentTriePointers(ProfileTrieNode* current,
|
||||
ProfileTrieNode* parent) {
|
||||
if (current == NULL) {
|
||||
ProfileTrieNode* root = GetTrieRoot(kInclusiveCode);
|
||||
AddParentTriePointers(root, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
current->set_parent(parent);
|
||||
for (int i = 0; i < current->NumChildren(); i++) {
|
||||
AddParentTriePointers(current->At(i), current);
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::PrintBacktrace(ProfileTrieNode* node, TextBuffer* buf) {
|
||||
ProfileTrieNode* current = node;
|
||||
while (current != NULL) {
|
||||
buf->AddString(current->ToCString(this));
|
||||
buf->AddString("\n");
|
||||
current = current->parent();
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::AddToTimeline() {
|
||||
TimelineStream* stream = Timeline::GetProfilerStream();
|
||||
if (stream == NULL) {
|
||||
return;
|
||||
}
|
||||
AddParentTriePointers(NULL, NULL);
|
||||
for (intptr_t sample_index = 0; sample_index < samples_->length();
|
||||
sample_index++) {
|
||||
TextBuffer buf(256);
|
||||
ProcessedSample* sample = samples_->At(sample_index);
|
||||
PrintBacktrace(sample->timeline_code_trie(), &buf);
|
||||
TimelineEvent* event = stream->StartEvent();
|
||||
event->Instant("Dart CPU sample", sample->timestamp());
|
||||
event->set_owns_label(false);
|
||||
event->SetNumArguments(1);
|
||||
event->SetArgument(0, "backtrace", buf.Steal());
|
||||
event->Complete();
|
||||
event = NULL; // Complete() deletes the event.
|
||||
}
|
||||
}
|
||||
|
||||
ProfileFunction* Profile::FindFunction(const Function& function) {
|
||||
return (functions_ != NULL) ? functions_->Lookup(function) : NULL;
|
||||
}
|
||||
|
@ -2685,7 +2745,7 @@ void ProfilerService::PrintJSONImpl(Thread* thread,
|
|||
intptr_t extra_tags,
|
||||
SampleFilter* filter,
|
||||
SampleBuffer* sample_buffer,
|
||||
bool as_timeline) {
|
||||
PrintKind kind) {
|
||||
Isolate* isolate = thread->isolate();
|
||||
// Disable thread interrupts while processing the buffer.
|
||||
DisableThreadInterruptsScope dtis(thread);
|
||||
|
@ -2700,10 +2760,12 @@ void ProfilerService::PrintJSONImpl(Thread* thread,
|
|||
HANDLESCOPE(thread);
|
||||
Profile profile(isolate);
|
||||
profile.Build(thread, filter, sample_buffer, tag_order, extra_tags);
|
||||
if (as_timeline) {
|
||||
if (kind == kAsTimeline) {
|
||||
profile.PrintTimelineJSON(stream);
|
||||
} else {
|
||||
} else if (kind == kAsProfile) {
|
||||
profile.PrintProfileJSON(stream);
|
||||
} else if (kind == kAsPlatformTimeline) {
|
||||
profile.AddToTimeline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2731,9 +2793,8 @@ void ProfilerService::PrintJSON(JSONStream* stream,
|
|||
Isolate* isolate = thread->isolate();
|
||||
NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask,
|
||||
time_origin_micros, time_extent_micros);
|
||||
const bool as_timeline = false;
|
||||
PrintJSONImpl(thread, stream, tag_order, extra_tags, &filter,
|
||||
Profiler::sample_buffer(), as_timeline);
|
||||
Profiler::sample_buffer(), kAsProfile);
|
||||
}
|
||||
|
||||
class ClassAllocationSampleFilter : public SampleFilter {
|
||||
|
@ -2770,9 +2831,8 @@ void ProfilerService::PrintAllocationJSON(JSONStream* stream,
|
|||
ClassAllocationSampleFilter filter(isolate->main_port(), cls,
|
||||
Thread::kMutatorTask, time_origin_micros,
|
||||
time_extent_micros);
|
||||
const bool as_timeline = false;
|
||||
PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter,
|
||||
Profiler::sample_buffer(), as_timeline);
|
||||
Profiler::sample_buffer(), kAsProfile);
|
||||
}
|
||||
|
||||
void ProfilerService::PrintNativeAllocationJSON(JSONStream* stream,
|
||||
|
@ -2781,9 +2841,8 @@ void ProfilerService::PrintNativeAllocationJSON(JSONStream* stream,
|
|||
int64_t time_extent_micros) {
|
||||
Thread* thread = Thread::Current();
|
||||
NativeAllocationSampleFilter filter(time_origin_micros, time_extent_micros);
|
||||
const bool as_timeline = false;
|
||||
PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter,
|
||||
Profiler::allocation_sample_buffer(), as_timeline);
|
||||
Profiler::allocation_sample_buffer(), kAsProfile);
|
||||
}
|
||||
|
||||
void ProfilerService::PrintTimelineJSON(JSONStream* stream,
|
||||
|
@ -2797,9 +2856,21 @@ void ProfilerService::PrintTimelineJSON(JSONStream* stream,
|
|||
Thread::kSweeperTask | Thread::kMarkerTask;
|
||||
NoAllocationSampleFilter filter(isolate->main_port(), thread_task_mask,
|
||||
time_origin_micros, time_extent_micros);
|
||||
const bool as_timeline = true;
|
||||
PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter,
|
||||
Profiler::sample_buffer(), as_timeline);
|
||||
Profiler::sample_buffer(), kAsTimeline);
|
||||
}
|
||||
|
||||
void ProfilerService::AddToTimeline() {
|
||||
JSONStream stream;
|
||||
Thread* thread = Thread::Current();
|
||||
Isolate* isolate = thread->isolate();
|
||||
const intptr_t thread_task_mask = Thread::kMutatorTask |
|
||||
Thread::kCompilerTask |
|
||||
Thread::kSweeperTask | Thread::kMarkerTask;
|
||||
NoAllocationSampleFilter filter(isolate->main_port(), thread_task_mask, -1,
|
||||
-1);
|
||||
PrintJSONImpl(thread, &stream, Profile::kNoTags, kNoExtraTags, &filter,
|
||||
Profiler::sample_buffer(), kAsPlatformTimeline);
|
||||
}
|
||||
|
||||
void ProfilerService::ClearSamples() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#ifndef RUNTIME_VM_PROFILER_SERVICE_H_
|
||||
#define RUNTIME_VM_PROFILER_SERVICE_H_
|
||||
|
||||
#include "platform/text_buffer.h"
|
||||
#include "vm/allocation.h"
|
||||
#include "vm/code_observers.h"
|
||||
#include "vm/globals.h"
|
||||
|
@ -32,6 +33,7 @@ class RawFunction;
|
|||
class SampleFilter;
|
||||
class ProcessedSample;
|
||||
class ProcessedSampleBuffer;
|
||||
class Profile;
|
||||
|
||||
class ProfileFunctionSourcePosition {
|
||||
public:
|
||||
|
@ -294,6 +296,8 @@ class ProfileTrieNode : public ZoneAllocated {
|
|||
|
||||
virtual void PrintToJSONArray(JSONArray* array) const = 0;
|
||||
|
||||
virtual const char* ToCString(Profile* profile) const = 0;
|
||||
|
||||
// Index into function or code tables.
|
||||
intptr_t table_index() const { return table_index_; }
|
||||
|
||||
|
@ -316,6 +320,9 @@ class ProfileTrieNode : public ZoneAllocated {
|
|||
|
||||
ProfileTrieNode* At(intptr_t i) { return children_.At(i); }
|
||||
|
||||
ProfileTrieNode* parent() const { return parent_; }
|
||||
void set_parent(ProfileTrieNode* p) { parent_ = p; }
|
||||
|
||||
intptr_t IndexOf(ProfileTrieNode* node);
|
||||
|
||||
intptr_t frame_id() const { return frame_id_; }
|
||||
|
@ -339,6 +346,7 @@ class ProfileTrieNode : public ZoneAllocated {
|
|||
intptr_t exclusive_allocations_;
|
||||
intptr_t inclusive_allocations_;
|
||||
ZoneGrowableArray<ProfileTrieNode*> children_;
|
||||
ProfileTrieNode* parent_;
|
||||
intptr_t frame_id_;
|
||||
|
||||
friend class ProfileBuilder;
|
||||
|
@ -388,9 +396,15 @@ class Profile : public ValueObject {
|
|||
void PrintProfileJSON(JSONStream* stream);
|
||||
void PrintTimelineJSON(JSONStream* stream);
|
||||
|
||||
// Serializes sample backtraces into arguments on Instant events and adds them
|
||||
// directly to the timeline.
|
||||
void AddToTimeline();
|
||||
|
||||
ProfileFunction* FindFunction(const Function& function);
|
||||
|
||||
private:
|
||||
void AddParentTriePointers(ProfileTrieNode* current, ProfileTrieNode* parent);
|
||||
void PrintBacktrace(ProfileTrieNode* node, TextBuffer* buf);
|
||||
void PrintHeaderJSON(JSONObject* obj);
|
||||
void PrintTimelineFrameJSON(JSONObject* frames,
|
||||
ProfileTrieNode* current,
|
||||
|
@ -485,16 +499,24 @@ class ProfilerService : public AllStatic {
|
|||
int64_t time_origin_micros,
|
||||
int64_t time_extent_micros);
|
||||
|
||||
static void AddToTimeline();
|
||||
|
||||
static void ClearSamples();
|
||||
|
||||
private:
|
||||
enum PrintKind {
|
||||
kAsProfile,
|
||||
kAsTimeline,
|
||||
kAsPlatformTimeline,
|
||||
};
|
||||
|
||||
static void PrintJSONImpl(Thread* thread,
|
||||
JSONStream* stream,
|
||||
Profile::TagOrder tag_order,
|
||||
intptr_t extra_tags,
|
||||
SampleFilter* filter,
|
||||
SampleBuffer* sample_buffer,
|
||||
bool as_timline);
|
||||
PrintKind kind);
|
||||
};
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -3806,6 +3806,11 @@ static const MethodParameter* get_cpu_profile_params[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static const MethodParameter* write_cpu_profile_timeline_params[] = {
|
||||
RUNNABLE_ISOLATE_PARAMETER,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// TODO(johnmccutchan): Rename this to GetCpuSamples.
|
||||
static bool GetCpuProfile(Thread* thread, JSONStream* js) {
|
||||
Profile::TagOrder tag_order =
|
||||
|
@ -3843,6 +3848,11 @@ static bool GetCpuProfileTimeline(Thread* thread, JSONStream* js) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool WriteCpuProfileTimeline(Thread* thread, JSONStream* js) {
|
||||
ProfilerService::AddToTimeline();
|
||||
return true;
|
||||
}
|
||||
|
||||
static const MethodParameter* get_allocation_samples_params[] = {
|
||||
RUNNABLE_ISOLATE_PARAMETER,
|
||||
new EnumParameter("tags", true, tags_enum_names),
|
||||
|
@ -4841,6 +4851,8 @@ static const ServiceMethodDescriptor service_methods_[] = {
|
|||
get_cpu_profile_params },
|
||||
{ "_getCpuProfileTimeline", GetCpuProfileTimeline,
|
||||
get_cpu_profile_timeline_params },
|
||||
{ "_writeCpuProfileTimeline", WriteCpuProfileTimeline,
|
||||
write_cpu_profile_timeline_params },
|
||||
{ "getFlagList", GetFlagList,
|
||||
get_flag_list_params },
|
||||
{ "_getHeapMap", GetHeapMap,
|
||||
|
|
|
@ -46,6 +46,7 @@ class Zone;
|
|||
V(Embedder, "dart:embedder") \
|
||||
V(GC, "dart:gc") \
|
||||
V(Isolate, "dart:isolate") \
|
||||
V(Profiler, "dart:profiler") \
|
||||
V(VM, "dart:vm")
|
||||
|
||||
// A stream of timeline events. A stream has a name and can be enabled or
|
||||
|
|
|
@ -62,8 +62,8 @@ application_snapshot("frontend_server") {
|
|||
|
||||
prebuilt_dart_action("kernel_service_dill") {
|
||||
deps = [
|
||||
"../../runtime/vm:vm_platform",
|
||||
"../../runtime/vm:kernel_platform_files($dart_host_toolchain)",
|
||||
"../../runtime/vm:vm_platform",
|
||||
]
|
||||
kernel_service_script = "../../pkg/vm/bin/kernel_service.dart"
|
||||
gen_kernel_script = "../../pkg/vm/bin/gen_kernel.dart"
|
||||
|
@ -80,7 +80,7 @@ prebuilt_dart_action("kernel_service_dill") {
|
|||
|
||||
depfile = "$root_gen_dir/kernel_service_dill.d"
|
||||
abs_depfile = rebase_path(depfile)
|
||||
rebased_output = rebase_path(output, root_out_dir)
|
||||
rebased_output = rebase_path(output, root_build_dir)
|
||||
vm_args = [
|
||||
"--depfile=$abs_depfile",
|
||||
"--depfile_output_filename=$rebased_output",
|
||||
|
|
Loading…
Reference in a new issue