[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:
Zach Anderson 2019-03-07 21:28:26 +00:00 committed by commit-bot@chromium.org
parent ccf756dae5
commit 8332fb0631
9 changed files with 206 additions and 15 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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() {

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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",