// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #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" #include "vm/growable_array.h" #include "vm/object.h" #include "vm/profiler.h" #include "vm/tags.h" #include "vm/thread_interrupter.h" #include "vm/token_position.h" // CPU Profile model and service protocol bits. // NOTE: For sampling and stack walking related code, see profiler.h. namespace dart { // Forward declarations. class Code; class Function; class JSONArray; class JSONStream; class ProfileFunctionTable; class ProfileCodeTable; class RawCode; class RawFunction; class SampleFilter; class ProcessedSample; class ProcessedSampleBuffer; class Profile; class ProfileFunctionSourcePosition { public: explicit ProfileFunctionSourcePosition(TokenPosition token_pos); void Tick(bool exclusive); TokenPosition token_pos() const { return token_pos_; } intptr_t exclusive_ticks() const { return exclusive_ticks_; } intptr_t inclusive_ticks() const { return inclusive_ticks_; } private: TokenPosition token_pos_; intptr_t exclusive_ticks_; intptr_t inclusive_ticks_; DISALLOW_ALLOCATION(); }; // Profile data related to a |Function|. class ProfileFunction : public ZoneAllocated { public: enum Kind { kDartFunction, // Dart function. kNativeFunction, // Synthetic function for Native (C/C++). kTagFunction, // Synthetic function for a VM or User tag. kStubFunction, // Synthetic function for stub code. kUnknownFunction, // A singleton function for unknown objects. }; ProfileFunction(Kind kind, const char* name, const Function& function, const intptr_t table_index); const char* name() const { ASSERT(name_ != NULL); return name_; } const char* Name() const; const Function* function() const { return &function_; } // Returns the resolved_url for the script containing this function. const char* ResolvedScriptUrl() const; bool is_visible() const; intptr_t table_index() const { return table_index_; } Kind kind() const { return kind_; } intptr_t exclusive_ticks() const { return exclusive_ticks_; } intptr_t inclusive_ticks() const { return inclusive_ticks_; } void IncInclusiveTicks() { inclusive_ticks_++; } void Tick(bool exclusive, intptr_t inclusive_serial, TokenPosition token_position); static const char* KindToCString(Kind kind); void PrintToJSONArray(JSONArray* functions); // Returns true if the call was successful and |pfsp| is set. bool GetSinglePosition(ProfileFunctionSourcePosition* pfsp); void TickSourcePosition(TokenPosition token_position, bool exclusive); intptr_t NumSourcePositions() const { return source_position_ticks_.length(); } const ProfileFunctionSourcePosition& GetSourcePosition(intptr_t i) const { return source_position_ticks_.At(i); } private: const Kind kind_; const char* name_; const Function& function_; const intptr_t table_index_; ZoneGrowableArray profile_codes_; ZoneGrowableArray source_position_ticks_; intptr_t exclusive_ticks_; intptr_t inclusive_ticks_; intptr_t inclusive_serial_; void PrintToJSONObject(JSONObject* func); // A |ProfileCode| that contains this function. void AddProfileCode(intptr_t code_table_index); friend class ProfileCode; friend class ProfileBuilder; }; class ProfileCodeAddress { public: explicit ProfileCodeAddress(uword pc); void Tick(bool exclusive); uword pc() const { return pc_; } intptr_t exclusive_ticks() const { return exclusive_ticks_; } intptr_t inclusive_ticks() const { return inclusive_ticks_; } private: uword pc_; intptr_t exclusive_ticks_; intptr_t inclusive_ticks_; }; // Profile data related to a |Code|. class ProfileCode : public ZoneAllocated { public: enum Kind { kDartCode, // Live Dart code. kCollectedCode, // Dead Dart code. kNativeCode, // Native code. kReusedCode, // Dead Dart code that has been reused by new kDartCode. kTagCode, // A special kind of code representing a tag. }; ProfileCode(Kind kind, uword start, uword end, int64_t timestamp, const AbstractCode code); Kind kind() const { return kind_; } uword start() const { return start_; } void set_start(uword start) { start_ = start; } uword end() const { return end_; } void set_end(uword end) { end_ = end; } void ExpandLower(uword start); void ExpandUpper(uword end); void TruncateLower(uword start); void TruncateUpper(uword end); bool Contains(uword pc) const { return (pc >= start_) && (pc < end_); } bool Overlaps(const ProfileCode* other) const; int64_t compile_timestamp() const { return compile_timestamp_; } void set_compile_timestamp(int64_t timestamp) { compile_timestamp_ = timestamp; } intptr_t exclusive_ticks() const { return exclusive_ticks_; } void set_exclusive_ticks(intptr_t exclusive_ticks) { exclusive_ticks_ = exclusive_ticks; } void IncExclusiveTicks() { exclusive_ticks_++; } intptr_t inclusive_ticks() const { return inclusive_ticks_; } void set_inclusive_ticks(intptr_t inclusive_ticks) { inclusive_ticks_ = inclusive_ticks; } void IncInclusiveTicks() { inclusive_ticks_++; } bool IsOptimizedDart() const; const AbstractCode code() const { return code_; } const char* name() const { return name_; } void SetName(const char* name); void GenerateAndSetSymbolName(const char* prefix); static const char* KindToCString(Kind kind); void PrintToJSONArray(JSONArray* codes); ProfileFunction* function() const { return function_; } private: void Tick(uword pc, bool exclusive, intptr_t serial); void TickAddress(uword pc, bool exclusive); ProfileFunction* SetFunctionAndName(ProfileFunctionTable* table); void PrintNativeCode(JSONObject* profile_code_obj); void PrintCollectedCode(JSONObject* profile_code_obj); void PrintOverwrittenCode(JSONObject* profile_code_obj); void PrintTagCode(JSONObject* profile_code_obj); void set_code_table_index(intptr_t index) { code_table_index_ = index; } intptr_t code_table_index() const { return code_table_index_; } const Kind kind_; uword start_; uword end_; intptr_t exclusive_ticks_; intptr_t inclusive_ticks_; intptr_t inclusive_serial_; const AbstractCode code_; char* name_; int64_t compile_timestamp_; ProfileFunction* function_; intptr_t code_table_index_; ZoneGrowableArray address_ticks_; friend class ProfileBuilder; }; class ProfileCodeTable : public ZoneAllocated { public: ProfileCodeTable() : table_(8) {} intptr_t length() const { return table_.length(); } ProfileCode* At(intptr_t index) const { ASSERT(index >= 0); ASSERT(index < length()); return table_[index]; } // Find the table index to the ProfileCode containing pc. // Returns < 0 if not found. intptr_t FindCodeIndexForPC(uword pc) const; ProfileCode* FindCodeForPC(uword pc) const { intptr_t index = FindCodeIndexForPC(pc); if (index < 0) { return NULL; } return At(index); } // Insert |new_code| into the table. Returns the table index where |new_code| // was inserted. Will merge with an overlapping ProfileCode if one is present. intptr_t InsertCode(ProfileCode* new_code); private: void FindNeighbors(uword pc, intptr_t* lo, intptr_t* hi, ProfileCode** lo_code, ProfileCode** hi_code) const; void VerifyOrder(); void VerifyOverlap(); ZoneGrowableArray table_; }; // Stack traces are organized in a trie. This holds information about one node // in the trie. A node in a tree represents a stack frame and a path in the tree // represents a stack trace. Each unique stack trace appears in the tree once // and each node has a count indicating how many times this has been observed. // The index can be used to look up a |ProfileFunction| or |ProfileCode|. // A node can have zero or more children. Depending on the kind of trie the // children are callers or callees of the current node. class ProfileTrieNode : public ZoneAllocated { public: explicit ProfileTrieNode(intptr_t index); virtual ~ProfileTrieNode(); 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_; } intptr_t count() const { return count_; } void Tick(ProcessedSample* sample, bool exclusive = false); void IncrementAllocation(intptr_t allocation, bool exclusive) { ASSERT(allocation >= 0); if (exclusive) { exclusive_allocations_ += allocation; } inclusive_allocations_ += allocation; } intptr_t inclusive_allocations() const { return inclusive_allocations_; } intptr_t exclusive_allocations() const { return exclusive_allocations_; } intptr_t NumChildren() const { return children_.length(); } 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_; } void set_frame_id(intptr_t id) { ASSERT(frame_id_ == -1); frame_id_ = id; } protected: void SortChildren(); static int ProfileTrieNodeCompare(ProfileTrieNode* const* a, ProfileTrieNode* const* b) { ASSERT(a != NULL); ASSERT(b != NULL); return (*b)->count() - (*a)->count(); } intptr_t table_index_; intptr_t count_; intptr_t exclusive_allocations_; intptr_t inclusive_allocations_; ZoneGrowableArray children_; ProfileTrieNode* parent_; intptr_t frame_id_; friend class ProfileBuilder; }; // The model for a profile. Most of the model is zone allocated, therefore // a zone must be created that lives longer than this object. class Profile : public ValueObject { public: enum TagOrder { kNoTags, kUser, kUserVM, kVM, kVMUser }; enum TrieKind { kExclusiveCode, kExclusiveFunction, kInclusiveCode, kInclusiveFunction, kNumTrieKinds, }; static bool IsCodeTrie(TrieKind kind) { return (kind == kExclusiveCode) || (kind == kInclusiveCode); } static bool IsFunctionTrie(TrieKind kind) { return !IsCodeTrie(kind); } explicit Profile(Isolate* isolate); // Build a filtered model using |filter| with the specified |tag_order|. void Build(Thread* thread, SampleFilter* filter, SampleBuffer* sample_buffer, TagOrder tag_order, intptr_t extra_tags = 0); // After building: int64_t min_time() const { return min_time_; } int64_t max_time() const { return max_time_; } int64_t GetTimeSpan() const { return max_time() - min_time(); } intptr_t sample_count() const { return sample_count_; } intptr_t NumFunctions() const; ProfileFunction* GetFunction(intptr_t index); ProfileCode* GetCode(intptr_t index); ProfileTrieNode* GetTrieRoot(TrieKind trie_kind); void PrintProfileJSON(JSONStream* stream); void PrintTimelineJSON(JSONStream* stream, bool code_trie); // Serializes sample backtraces into arguments on Instant events and adds them // directly to the timeline. void AddToTimeline(bool code_trie); ProfileFunction* FindFunction(const Function& function); private: void AddParentTriePointers(ProfileTrieNode* current, ProfileTrieNode* parent, bool code_trie); void PrintBacktrace(ProfileTrieNode* node, TextBuffer* buf); void PrintHeaderJSON(JSONObject* obj); void PrintTimelineFrameJSON(JSONObject* frames, ProfileTrieNode* current, ProfileTrieNode* parent, intptr_t* next_id, bool code_trie); Isolate* isolate_; Zone* zone_; ProcessedSampleBuffer* samples_; ProfileCodeTable* live_code_; ProfileCodeTable* dead_code_; ProfileCodeTable* tag_code_; ProfileFunctionTable* functions_; intptr_t dead_code_index_offset_; intptr_t tag_code_index_offset_; ProfileTrieNode* roots_[kNumTrieKinds]; int64_t min_time_; int64_t max_time_; intptr_t sample_count_; friend class ProfileBuilder; }; class ProfileTrieWalker : public ValueObject { public: explicit ProfileTrieWalker(Profile* profile) : profile_(profile), parent_(NULL), current_(NULL), code_trie_(false) { ASSERT(profile_ != NULL); } void Reset(Profile::TrieKind trie_kind); const char* CurrentName(); // Return the current node's peer's inclusive tick count. intptr_t CurrentInclusiveTicks(); // Return the current node's peer's exclusive tick count. intptr_t CurrentExclusiveTicks(); // Return the current node's inclusive allocation count. intptr_t CurrentInclusiveAllocations(); // Return the current node's exclusive allocation count. intptr_t CurrentExclusiveAllocations(); // Return the current node's tick count. intptr_t CurrentNodeTickCount(); // Return the number siblings (including yourself). intptr_t SiblingCount(); // If the following conditions are met returns the current token: // 1) This is a function trie. // 2) There is only one token position for a given function. // Will return NULL otherwise. const char* CurrentToken(); bool Down(); bool NextSibling(); private: Profile* profile_; ProfileTrieNode* parent_; ProfileTrieNode* current_; bool code_trie_; }; class ProfilerService : public AllStatic { public: enum { kNoExtraTags = 0, kCodeTransitionTagsBit = (1 << 0), }; static void PrintJSON(JSONStream* stream, Profile::TagOrder tag_order, intptr_t extra_tags, int64_t time_origin_micros, int64_t time_extent_micros); static void PrintAllocationJSON(JSONStream* stream, Profile::TagOrder tag_order, const Class& cls, int64_t time_origin_micros, int64_t time_extent_micros); static void PrintNativeAllocationJSON(JSONStream* stream, Profile::TagOrder tag_order, int64_t time_origin_micros, int64_t time_extent_micros); static void PrintTimelineJSON(JSONStream* stream, Profile::TagOrder tag_order, int64_t time_origin_micros, int64_t time_extent_micros, bool code_trie); static void AddToTimeline(Profile::TagOrder tag_order, int64_t time_origin_micros, int64_t time_extent_micros, bool code_trie); 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, PrintKind kind, bool code_trie); }; } // namespace dart #endif // RUNTIME_VM_PROFILER_SERVICE_H_