|  | // 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" | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | #include "perfetto/protozero/scattered_heap_buffer.h" | 
|  | #include "vm/protos/perfetto/trace/profiling/profile_common.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/trace_packet.pbzero.h" | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | // 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 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(); | 
|  | }; | 
|  |  | 
|  | class ProfileCodeInlinedFunctionsCache : public ZoneAllocated { | 
|  | public: | 
|  | ProfileCodeInlinedFunctionsCache() : cache_cursor_(0), last_hit_(0) { | 
|  | for (intptr_t i = 0; i < kCacheSize; i++) { | 
|  | cache_[i].Reset(); | 
|  | } | 
|  | cache_hit_ = 0; | 
|  | cache_miss_ = 0; | 
|  | } | 
|  |  | 
|  | ~ProfileCodeInlinedFunctionsCache() { | 
|  | if (FLAG_trace_profiler) { | 
|  | intptr_t total = cache_hit_ + cache_miss_; | 
|  | OS::PrintErr("LOOKUPS: %" Pd " HITS: %" Pd " MISSES: %" Pd "\n", total, | 
|  | cache_hit_, cache_miss_); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Get(uword pc, | 
|  | const Code& code, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index, | 
|  | // Outputs: | 
|  | GrowableArray<const Function*>** inlined_functions, | 
|  | GrowableArray<TokenPosition>** inlined_token_positions, | 
|  | TokenPosition* token_position); | 
|  |  | 
|  | private: | 
|  | bool FindInCache(uword pc, | 
|  | intptr_t offset, | 
|  | GrowableArray<const Function*>** inlined_functions, | 
|  | GrowableArray<TokenPosition>** inlined_token_positions, | 
|  | TokenPosition* token_position); | 
|  |  | 
|  | // Add to cache and fill in outputs. | 
|  | void Add(uword pc, | 
|  | const Code& code, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index, | 
|  | // Outputs: | 
|  | GrowableArray<const Function*>** inlined_functions, | 
|  | GrowableArray<TokenPosition>** inlined_token_positions, | 
|  | TokenPosition* token_position); | 
|  |  | 
|  | intptr_t NextFreeIndex() { | 
|  | cache_cursor_ = (cache_cursor_ + 1) % kCacheSize; | 
|  | return cache_cursor_; | 
|  | } | 
|  |  | 
|  | intptr_t OffsetForPC(uword pc, | 
|  | const Code& code, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index); | 
|  | struct CacheEntry { | 
|  | void Reset() { | 
|  | pc = 0; | 
|  | offset = 0; | 
|  | inlined_functions.Clear(); | 
|  | inlined_token_positions.Clear(); | 
|  | } | 
|  | uword pc; | 
|  | intptr_t offset; | 
|  | GrowableArray<const Function*> inlined_functions; | 
|  | GrowableArray<TokenPosition> inlined_token_positions; | 
|  | TokenPosition token_position = TokenPosition::kNoSource; | 
|  | }; | 
|  |  | 
|  | static constexpr intptr_t kCacheSize = 128; | 
|  | intptr_t cache_cursor_; | 
|  | intptr_t last_hit_; | 
|  | CacheEntry cache_[kCacheSize]; | 
|  | intptr_t cache_miss_; | 
|  | intptr_t cache_hit_; | 
|  | }; | 
|  |  | 
|  | // 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_ != nullptr); | 
|  | 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, bool print_only_ids = false); | 
|  |  | 
|  | // 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<intptr_t> profile_codes_; | 
|  | ZoneGrowableArray<ProfileFunctionSourcePosition> 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_; } | 
|  |  | 
|  | intptr_t code_table_index() const { return code_table_index_; } | 
|  |  | 
|  | 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; } | 
|  |  | 
|  | 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<ProfileCodeAddress> 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 nullptr; | 
|  | } | 
|  | 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<ProfileCode*> table_; | 
|  | }; | 
|  |  | 
|  | // 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: | 
|  | Profile(); | 
|  |  | 
|  | // Build a filtered model using |filter|. | 
|  | void Build(Thread* thread, | 
|  | Isolate* isolate, | 
|  | SampleFilter* filter, | 
|  | SampleBlockBuffer* sample_block_buffer); | 
|  |  | 
|  | // 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_; } | 
|  | ProcessedSample* SampleAt(intptr_t index); | 
|  |  | 
|  | intptr_t NumFunctions() const; | 
|  |  | 
|  | ProfileFunction* GetFunction(intptr_t index); | 
|  | ProfileCode* GetCode(intptr_t index); | 
|  | ProfileCode* GetCodeFromPC(uword pc, int64_t timestamp); | 
|  |  | 
|  | void PrintProfileJSON(JSONStream* stream, bool include_code_samples); | 
|  | void PrintProfileJSON(JSONObject* obj, | 
|  | bool include_code_samples, | 
|  | bool is_event = false); | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | void PrintProfilePerfetto(JSONStream* js); | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | ProfileFunction* FindFunction(const Function& function); | 
|  |  | 
|  | private: | 
|  | void PrintHeaderJSON(JSONObject* obj); | 
|  | void ProcessSampleFrameJSON(JSONArray* stack, | 
|  | ProfileCodeInlinedFunctionsCache* cache, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index); | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | void ProcessSampleFramePerfetto( | 
|  | perfetto::protos::pbzero::Callstack* callstack, | 
|  | ProfileCodeInlinedFunctionsCache* cache, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index); | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | void ProcessInlinedFunctionFrameJSON(JSONArray* stack, | 
|  | const Function* inlined_function); | 
|  | void PrintFunctionFrameIndexJSON(JSONArray* stack, ProfileFunction* function); | 
|  | void PrintCodeFrameIndexJSON(JSONArray* stack, | 
|  | ProcessedSample* sample, | 
|  | intptr_t frame_index); | 
|  | void PrintSamplesJSON(JSONObject* obj, bool code_samples); | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | /* | 
|  | * Appends Perfetto packets describing the CPU samples in this profile to | 
|  | * |jsonBase64String|. The |packet| parameter allows us to reuse an existing | 
|  | * heap-buffered packet to avoid allocating a new one. | 
|  | */ | 
|  | void PrintSamplesPerfetto( | 
|  | JSONBase64String* jsonBase64String, | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet); | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | 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_; | 
|  |  | 
|  | int64_t min_time_; | 
|  | int64_t max_time_; | 
|  |  | 
|  | intptr_t sample_count_; | 
|  |  | 
|  | friend class ProfileBuilder; | 
|  | }; | 
|  |  | 
|  | class ProfilerService : public AllStatic { | 
|  | public: | 
|  | /* | 
|  | * Prints a CpuSamples service response into |js|. | 
|  | */ | 
|  | static void PrintJSON(JSONStream* stream, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros, | 
|  | bool include_code_samples); | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | /* | 
|  | * Prints a PerfettoCpuSamples service response into |js|. | 
|  | */ | 
|  | static void PrintPerfetto(JSONStream* js, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros); | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | static void PrintAllocationJSON(JSONStream* stream, | 
|  | const Class& cls, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros); | 
|  |  | 
|  | static void PrintAllocationJSON(JSONStream* stream, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros); | 
|  |  | 
|  | static void ClearSamples(); | 
|  |  | 
|  | private: | 
|  | enum PrintFormat : bool { JSON = false, Perfetto = true }; | 
|  |  | 
|  | static void PrintCommonImpl(PrintFormat format, | 
|  | Thread* thread, | 
|  | JSONStream* js, | 
|  | SampleFilter* filter, | 
|  | SampleBlockBuffer* buffer, | 
|  | bool include_code_samples); | 
|  |  | 
|  | /* | 
|  | * If |format| is |PrintFormat::JSON|, prints a CpuSamples service response | 
|  | * into |js|. If |format| is |PrintFormat::Perfetto|, prints a | 
|  | * PerfettoCpuSamples service response into |js|. Note that the value of | 
|  | * |include_code_samples| is ignored when |format| is |PrintFormat::Perfetto|. | 
|  | */ | 
|  | static void PrintCommon(PrintFormat format, | 
|  | JSONStream* js, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros, | 
|  | bool include_code_samples = false); | 
|  | }; | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // RUNTIME_VM_PROFILER_SERVICE_H_ |