| // Copyright (c) 2013, 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_H_ |
| #define RUNTIME_VM_PROFILER_H_ |
| |
| #include "platform/atomic.h" |
| |
| #include "vm/allocation.h" |
| #include "vm/bitfield.h" |
| #include "vm/code_observers.h" |
| #include "vm/globals.h" |
| #include "vm/growable_array.h" |
| #include "vm/malloc_hooks.h" |
| #include "vm/native_symbol.h" |
| #include "vm/object.h" |
| #include "vm/tags.h" |
| #include "vm/thread_interrupter.h" |
| |
| // Profiler sampling and stack walking support. |
| // NOTE: For service related code, see profile_service.h. |
| |
| namespace dart { |
| |
| // Forward declarations. |
| class ProcessedSample; |
| class ProcessedSampleBuffer; |
| |
| class Sample; |
| class AllocationSampleBuffer; |
| class SampleBuffer; |
| class ProfileTrieNode; |
| |
| #define PROFILER_COUNTERS(V) \ |
| V(bail_out_unknown_task) \ |
| V(bail_out_jump_to_exception_handler) \ |
| V(bail_out_check_isolate) \ |
| V(single_frame_sample_deoptimizing) \ |
| V(single_frame_sample_register_check) \ |
| V(single_frame_sample_get_and_validate_stack_bounds) \ |
| V(stack_walker_native) \ |
| V(stack_walker_dart_exit) \ |
| V(stack_walker_dart) \ |
| V(stack_walker_none) \ |
| V(incomplete_sample_fp_bounds) \ |
| V(incomplete_sample_fp_step) \ |
| V(incomplete_sample_bad_pc) \ |
| V(failure_native_allocation_sample) |
| |
| struct ProfilerCounters { |
| #define DECLARE_PROFILER_COUNTER(name) RelaxedAtomic<int64_t> name; |
| PROFILER_COUNTERS(DECLARE_PROFILER_COUNTER) |
| #undef DECLARE_PROFILER_COUNTER |
| }; |
| |
| class Profiler : public AllStatic { |
| public: |
| static void Init(); |
| static void InitAllocationSampleBuffer(); |
| static void Cleanup(); |
| |
| static void SetSampleDepth(intptr_t depth); |
| static void SetSamplePeriod(intptr_t period); |
| // Restarts sampling with a given profile period. This is called after the |
| // profile period is changed via the service protocol. |
| static void UpdateSamplePeriod(); |
| // Starts or shuts down the profiler after --profiler is changed via the |
| // service protocol. |
| static void UpdateRunningState(); |
| |
| static SampleBuffer* sample_buffer() { return sample_buffer_; } |
| static AllocationSampleBuffer* allocation_sample_buffer() { |
| return allocation_sample_buffer_; |
| } |
| |
| static void DumpStackTrace(void* context); |
| static void DumpStackTrace(bool for_crash = true); |
| |
| static void SampleAllocation(Thread* thread, intptr_t cid); |
| static Sample* SampleNativeAllocation(intptr_t skip_count, |
| uword address, |
| uintptr_t allocation_size); |
| |
| // SampleThread is called from inside the signal handler and hence it is very |
| // critical that the implementation of SampleThread does not do any of the |
| // following: |
| // * Accessing TLS -- Because on Windows and Fuchsia the callback will be |
| // running in a different thread. |
| // * Allocating memory -- Because this takes locks which may already be |
| // held, resulting in a dead lock. |
| // * Taking a lock -- See above. |
| static void SampleThread(Thread* thread, const InterruptedThreadState& state); |
| |
| static ProfilerCounters counters() { |
| // Copies the counter values. |
| return counters_; |
| } |
| inline static intptr_t Size(); |
| |
| private: |
| static void DumpStackTrace(uword sp, uword fp, uword pc, bool for_crash); |
| |
| // Calculates the sample buffer capacity. Returns |
| // SampleBuffer::kDefaultBufferCapacity if --sample-buffer-duration is not |
| // provided. Otherwise, the capacity is based on the sample rate, maximum |
| // sample stack depth, and the number of seconds of samples the sample buffer |
| // should be able to accomodate. |
| static intptr_t CalculateSampleBufferCapacity(); |
| |
| // Does not walk the thread's stack. |
| static void SampleThreadSingleFrame(Thread* thread, uintptr_t pc); |
| static RelaxedAtomic<bool> initialized_; |
| |
| static SampleBuffer* sample_buffer_; |
| static AllocationSampleBuffer* allocation_sample_buffer_; |
| |
| static ProfilerCounters counters_; |
| |
| friend class Thread; |
| }; |
| |
| class SampleVisitor : public ValueObject { |
| public: |
| explicit SampleVisitor(Dart_Port port) : port_(port), visited_(0) {} |
| virtual ~SampleVisitor() {} |
| |
| virtual void VisitSample(Sample* sample) = 0; |
| |
| intptr_t visited() const { return visited_; } |
| |
| void IncrementVisited() { visited_++; } |
| |
| Dart_Port port() const { return port_; } |
| |
| private: |
| Dart_Port port_; |
| intptr_t visited_; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(SampleVisitor); |
| }; |
| |
| class SampleFilter : public ValueObject { |
| public: |
| SampleFilter(Dart_Port port, |
| intptr_t thread_task_mask, |
| int64_t time_origin_micros, |
| int64_t time_extent_micros) |
| : port_(port), |
| thread_task_mask_(thread_task_mask), |
| time_origin_micros_(time_origin_micros), |
| time_extent_micros_(time_extent_micros) { |
| ASSERT(thread_task_mask != 0); |
| ASSERT(time_origin_micros_ >= -1); |
| ASSERT(time_extent_micros_ >= -1); |
| } |
| virtual ~SampleFilter() {} |
| |
| // Override this function. |
| // Return |true| if |sample| passes the filter. |
| virtual bool FilterSample(Sample* sample) { return true; } |
| |
| Dart_Port port() const { return port_; } |
| |
| // Returns |true| if |sample| passes the time filter. |
| bool TimeFilterSample(Sample* sample); |
| |
| // Returns |true| if |sample| passes the thread task filter. |
| bool TaskFilterSample(Sample* sample); |
| |
| static const intptr_t kNoTaskFilter = -1; |
| |
| private: |
| Dart_Port port_; |
| intptr_t thread_task_mask_; |
| int64_t time_origin_micros_; |
| int64_t time_extent_micros_; |
| }; |
| |
| class ClearProfileVisitor : public SampleVisitor { |
| public: |
| explicit ClearProfileVisitor(Isolate* isolate); |
| |
| virtual void VisitSample(Sample* sample); |
| }; |
| |
| // Each Sample holds a stack trace from an isolate. |
| class Sample { |
| public: |
| void Init(Dart_Port port, int64_t timestamp, ThreadId tid) { |
| Clear(); |
| timestamp_ = timestamp; |
| tid_ = tid; |
| port_ = port; |
| } |
| |
| Dart_Port port() const { return port_; } |
| |
| // Thread sample was taken on. |
| ThreadId tid() const { return tid_; } |
| |
| void Clear() { |
| port_ = ILLEGAL_PORT; |
| pc_marker_ = 0; |
| for (intptr_t i = 0; i < kStackBufferSizeInWords; i++) { |
| stack_buffer_[i] = 0; |
| } |
| vm_tag_ = VMTag::kInvalidTagId; |
| user_tag_ = UserTags::kDefaultUserTag; |
| lr_ = 0; |
| metadata_ = 0; |
| state_ = 0; |
| native_allocation_address_ = 0; |
| native_allocation_size_bytes_ = 0; |
| continuation_index_ = -1; |
| next_free_ = NULL; |
| uword* pcs = GetPCArray(); |
| for (intptr_t i = 0; i < pcs_length_; i++) { |
| pcs[i] = 0; |
| } |
| set_head_sample(true); |
| } |
| |
| // Timestamp sample was taken at. |
| int64_t timestamp() const { return timestamp_; } |
| |
| // Top most pc. |
| uword pc() const { return At(0); } |
| |
| // Get stack trace entry. |
| uword At(intptr_t i) const { |
| ASSERT(i >= 0); |
| ASSERT(i < pcs_length_); |
| uword* pcs = GetPCArray(); |
| return pcs[i]; |
| } |
| |
| // Set stack trace entry. |
| void SetAt(intptr_t i, uword pc) { |
| ASSERT(i >= 0); |
| ASSERT(i < pcs_length_); |
| uword* pcs = GetPCArray(); |
| pcs[i] = pc; |
| } |
| |
| void DumpStackTrace() { |
| for (intptr_t i = 0; i < pcs_length_; ++i) { |
| uintptr_t start = 0; |
| uword pc = At(i); |
| char* native_symbol_name = |
| NativeSymbolResolver::LookupSymbolName(pc, &start); |
| if (native_symbol_name == NULL) { |
| OS::PrintErr(" [0x%" Pp "] Unknown symbol\n", pc); |
| } else { |
| OS::PrintErr(" [0x%" Pp "] %s\n", pc, native_symbol_name); |
| NativeSymbolResolver::FreeSymbolName(native_symbol_name); |
| } |
| } |
| } |
| |
| uword vm_tag() const { return vm_tag_; } |
| void set_vm_tag(uword tag) { |
| ASSERT(tag != VMTag::kInvalidTagId); |
| vm_tag_ = tag; |
| } |
| |
| uword user_tag() const { return user_tag_; } |
| void set_user_tag(uword tag) { user_tag_ = tag; } |
| |
| uword pc_marker() const { return pc_marker_; } |
| |
| void set_pc_marker(uword pc_marker) { pc_marker_ = pc_marker; } |
| |
| uword lr() const { return lr_; } |
| |
| void set_lr(uword link_register) { lr_ = link_register; } |
| |
| bool leaf_frame_is_dart() const { return LeafFrameIsDart::decode(state_); } |
| |
| void set_leaf_frame_is_dart(bool leaf_frame_is_dart) { |
| state_ = LeafFrameIsDart::update(leaf_frame_is_dart, state_); |
| } |
| |
| bool ignore_sample() const { return IgnoreBit::decode(state_); } |
| |
| void set_ignore_sample(bool ignore_sample) { |
| state_ = IgnoreBit::update(ignore_sample, state_); |
| } |
| |
| bool exit_frame_sample() const { return ExitFrameBit::decode(state_); } |
| |
| void set_exit_frame_sample(bool exit_frame_sample) { |
| state_ = ExitFrameBit::update(exit_frame_sample, state_); |
| } |
| |
| bool missing_frame_inserted() const { |
| return MissingFrameInsertedBit::decode(state_); |
| } |
| |
| void set_missing_frame_inserted(bool missing_frame_inserted) { |
| state_ = MissingFrameInsertedBit::update(missing_frame_inserted, state_); |
| } |
| |
| bool truncated_trace() const { return TruncatedTraceBit::decode(state_); } |
| |
| void set_truncated_trace(bool truncated_trace) { |
| state_ = TruncatedTraceBit::update(truncated_trace, state_); |
| } |
| |
| bool is_allocation_sample() const { |
| return ClassAllocationSampleBit::decode(state_); |
| } |
| |
| void set_is_allocation_sample(bool allocation_sample) { |
| state_ = ClassAllocationSampleBit::update(allocation_sample, state_); |
| } |
| |
| uword native_allocation_address() const { return native_allocation_address_; } |
| |
| void set_native_allocation_address(uword address) { |
| native_allocation_address_ = address; |
| } |
| |
| uintptr_t native_allocation_size_bytes() const { |
| return native_allocation_size_bytes_; |
| } |
| |
| void set_native_allocation_size_bytes(uintptr_t size) { |
| native_allocation_size_bytes_ = size; |
| } |
| |
| Sample* next_free() const { return next_free_; } |
| void set_next_free(Sample* next_free) { next_free_ = next_free; } |
| |
| Thread::TaskKind thread_task() const { return ThreadTaskBit::decode(state_); } |
| |
| void set_thread_task(Thread::TaskKind task) { |
| state_ = ThreadTaskBit::update(task, state_); |
| } |
| |
| bool is_continuation_sample() const { |
| return ContinuationSampleBit::decode(state_); |
| } |
| |
| void SetContinuationIndex(intptr_t index) { |
| ASSERT(!is_continuation_sample()); |
| ASSERT(continuation_index_ == -1); |
| state_ = ContinuationSampleBit::update(true, state_); |
| continuation_index_ = index; |
| ASSERT(is_continuation_sample()); |
| } |
| |
| intptr_t continuation_index() const { |
| ASSERT(is_continuation_sample()); |
| return continuation_index_; |
| } |
| |
| intptr_t allocation_cid() const { |
| ASSERT(is_allocation_sample()); |
| return metadata_; |
| } |
| |
| void set_head_sample(bool head_sample) { |
| state_ = HeadSampleBit::update(head_sample, state_); |
| } |
| |
| bool head_sample() const { return HeadSampleBit::decode(state_); } |
| |
| void set_metadata(intptr_t metadata) { metadata_ = metadata; } |
| |
| void SetAllocationCid(intptr_t cid) { |
| set_is_allocation_sample(true); |
| set_metadata(cid); |
| } |
| |
| static void Init(); |
| |
| static intptr_t instance_size() { return instance_size_; } |
| |
| uword* GetPCArray() const; |
| |
| static const int kStackBufferSizeInWords = 2; |
| uword* GetStackBuffer() { return &stack_buffer_[0]; } |
| |
| private: |
| static intptr_t instance_size_; |
| static intptr_t pcs_length_; |
| enum StateBits { |
| kHeadSampleBit = 0, |
| kLeafFrameIsDartBit = 1, |
| kIgnoreBit = 2, |
| kExitFrameBit = 3, |
| kMissingFrameInsertedBit = 4, |
| kTruncatedTraceBit = 5, |
| kClassAllocationSampleBit = 6, |
| kContinuationSampleBit = 7, |
| kThreadTaskBit = 8, // 6 bits. |
| kNextFreeBit = 14, |
| }; |
| class HeadSampleBit : public BitField<uword, bool, kHeadSampleBit, 1> {}; |
| class LeafFrameIsDart : public BitField<uword, bool, kLeafFrameIsDartBit, 1> { |
| }; |
| class IgnoreBit : public BitField<uword, bool, kIgnoreBit, 1> {}; |
| class ExitFrameBit : public BitField<uword, bool, kExitFrameBit, 1> {}; |
| class MissingFrameInsertedBit |
| : public BitField<uword, bool, kMissingFrameInsertedBit, 1> {}; |
| class TruncatedTraceBit |
| : public BitField<uword, bool, kTruncatedTraceBit, 1> {}; |
| class ClassAllocationSampleBit |
| : public BitField<uword, bool, kClassAllocationSampleBit, 1> {}; |
| class ContinuationSampleBit |
| : public BitField<uword, bool, kContinuationSampleBit, 1> {}; |
| class ThreadTaskBit |
| : public BitField<uword, Thread::TaskKind, kThreadTaskBit, 6> {}; |
| |
| int64_t timestamp_; |
| ThreadId tid_; |
| Dart_Port port_; |
| uword pc_marker_; |
| uword stack_buffer_[kStackBufferSizeInWords]; |
| uword vm_tag_; |
| uword user_tag_; |
| uword metadata_; |
| uword lr_; |
| uword state_; |
| uword native_allocation_address_; |
| uintptr_t native_allocation_size_bytes_; |
| intptr_t continuation_index_; |
| Sample* next_free_; |
| |
| /* There are a variable number of words that follow, the words hold the |
| * sampled pc values. Access via GetPCArray() */ |
| DISALLOW_COPY_AND_ASSIGN(Sample); |
| }; |
| |
| class NativeAllocationSampleFilter : public SampleFilter { |
| public: |
| NativeAllocationSampleFilter(int64_t time_origin_micros, |
| int64_t time_extent_micros) |
| : SampleFilter(ILLEGAL_PORT, |
| SampleFilter::kNoTaskFilter, |
| time_origin_micros, |
| time_extent_micros) {} |
| |
| bool FilterSample(Sample* sample) { |
| // If the sample is an allocation sample, we need to check that the |
| // memory at the address hasn't been freed, and if the address associated |
| // with the allocation has been freed and then reissued. |
| void* alloc_address = |
| reinterpret_cast<void*>(sample->native_allocation_address()); |
| ASSERT(alloc_address != NULL); |
| Sample* recorded_sample = MallocHooks::GetSample(alloc_address); |
| return (sample == recorded_sample); |
| } |
| }; |
| |
| class AbstractCode { |
| public: |
| explicit AbstractCode(ObjectPtr code) : code_(Object::Handle(code)) { |
| ASSERT(code_.IsNull() || code_.IsCode() || code_.IsBytecode()); |
| } |
| |
| ObjectPtr raw() const { return code_.raw(); } |
| const Object* handle() const { return &code_; } |
| |
| uword PayloadStart() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).PayloadStart(); |
| } else { |
| return Bytecode::Cast(code_).PayloadStart(); |
| } |
| } |
| |
| uword Size() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).Size(); |
| } else { |
| return Bytecode::Cast(code_).Size(); |
| } |
| } |
| |
| int64_t compile_timestamp() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).compile_timestamp(); |
| } else { |
| return 0; |
| } |
| } |
| |
| const char* Name() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).Name(); |
| } else if (code_.IsBytecode()) { |
| return Bytecode::Cast(code_).Name(); |
| } else { |
| return ""; |
| } |
| } |
| |
| const char* QualifiedName() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).QualifiedName( |
| NameFormattingParams(Object::kUserVisibleName)); |
| } else if (code_.IsBytecode()) { |
| return Bytecode::Cast(code_).QualifiedName(); |
| } else { |
| return ""; |
| } |
| } |
| |
| bool IsStubCode() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).IsStubCode(); |
| } else if (code_.IsBytecode()) { |
| return (Bytecode::Cast(code_).function() == Function::null()); |
| } else { |
| return false; |
| } |
| } |
| |
| bool IsAllocationStubCode() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).IsAllocationStubCode(); |
| } else { |
| return false; |
| } |
| } |
| |
| bool IsTypeTestStubCode() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).IsTypeTestStubCode(); |
| } else { |
| return false; |
| } |
| } |
| |
| ObjectPtr owner() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).owner(); |
| } else if (code_.IsBytecode()) { |
| return Bytecode::Cast(code_).function(); |
| } else { |
| return Object::null(); |
| } |
| } |
| |
| bool IsNull() const { return code_.IsNull(); } |
| bool IsCode() const { return code_.IsCode(); } |
| bool IsBytecode() const { return code_.IsBytecode(); } |
| |
| bool is_optimized() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).is_optimized(); |
| } else { |
| return false; |
| } |
| } |
| |
| private: |
| const Object& code_; |
| }; |
| |
| // A Code object descriptor. |
| class CodeDescriptor : public ZoneAllocated { |
| public: |
| explicit CodeDescriptor(const AbstractCode code); |
| |
| uword Start() const; |
| |
| uword Size() const; |
| |
| int64_t CompileTimestamp() const; |
| |
| const AbstractCode code() const { return code_; } |
| |
| const char* Name() const { return code_.Name(); } |
| |
| bool Contains(uword pc) const { |
| uword end = Start() + Size(); |
| return (pc >= Start()) && (pc < end); |
| } |
| |
| static int Compare(CodeDescriptor* const* a, CodeDescriptor* const* b) { |
| ASSERT(a != NULL); |
| ASSERT(b != NULL); |
| |
| uword a_start = (*a)->Start(); |
| uword b_start = (*b)->Start(); |
| |
| if (a_start < b_start) { |
| return -1; |
| } else if (a_start > b_start) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| private: |
| const AbstractCode code_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CodeDescriptor); |
| }; |
| |
| // Fast lookup of Dart code objects. |
| class CodeLookupTable : public ZoneAllocated { |
| public: |
| explicit CodeLookupTable(Thread* thread); |
| |
| intptr_t length() const { return code_objects_.length(); } |
| |
| const CodeDescriptor* At(intptr_t index) const { |
| return code_objects_.At(index); |
| } |
| |
| const CodeDescriptor* FindCode(uword pc) const; |
| |
| private: |
| void Build(Thread* thread); |
| |
| void Add(const Object& code); |
| |
| // Code objects sorted by entry. |
| ZoneGrowableArray<CodeDescriptor*> code_objects_; |
| |
| friend class CodeLookupTableBuilder; |
| |
| DISALLOW_COPY_AND_ASSIGN(CodeLookupTable); |
| }; |
| |
| // Ring buffer of Samples that is (usually) shared by many isolates. |
| class SampleBuffer { |
| public: |
| // Up to 1 minute @ 1000Hz, less if samples are deep. |
| static const intptr_t kDefaultBufferCapacity = 60000; |
| |
| explicit SampleBuffer(intptr_t capacity = kDefaultBufferCapacity); |
| virtual ~SampleBuffer(); |
| |
| intptr_t capacity() const { return capacity_; } |
| |
| Sample* At(intptr_t idx) const; |
| intptr_t ReserveSampleSlot(); |
| virtual Sample* ReserveSample(); |
| virtual Sample* ReserveSampleAndLink(Sample* previous); |
| |
| void VisitSamples(SampleVisitor* visitor) { |
| ASSERT(visitor != NULL); |
| const intptr_t length = capacity(); |
| for (intptr_t i = 0; i < length; i++) { |
| Sample* sample = At(i); |
| if (!sample->head_sample()) { |
| // An inner sample in a chain of samples. |
| continue; |
| } |
| if (sample->ignore_sample()) { |
| // Bad sample. |
| continue; |
| } |
| if (sample->port() != visitor->port()) { |
| // Another isolate. |
| continue; |
| } |
| if (sample->timestamp() == 0) { |
| // Empty. |
| continue; |
| } |
| if (sample->At(0) == 0) { |
| // No frames. |
| continue; |
| } |
| visitor->IncrementVisited(); |
| visitor->VisitSample(sample); |
| } |
| } |
| |
| ProcessedSampleBuffer* BuildProcessedSampleBuffer(SampleFilter* filter); |
| |
| intptr_t Size() { return memory_->size(); } |
| |
| protected: |
| ProcessedSample* BuildProcessedSample(Sample* sample, |
| const CodeLookupTable& clt); |
| Sample* Next(Sample* sample); |
| |
| VirtualMemory* memory_; |
| Sample* samples_; |
| intptr_t capacity_; |
| RelaxedAtomic<uintptr_t> cursor_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SampleBuffer); |
| }; |
| |
| class AllocationSampleBuffer : public SampleBuffer { |
| public: |
| explicit AllocationSampleBuffer(intptr_t capacity = kDefaultBufferCapacity); |
| virtual ~AllocationSampleBuffer(); |
| |
| intptr_t ReserveSampleSlotLocked(); |
| virtual Sample* ReserveSample(); |
| virtual Sample* ReserveSampleAndLink(Sample* previous); |
| void FreeAllocationSample(Sample* sample); |
| |
| private: |
| Mutex mutex_; |
| Sample* free_sample_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AllocationSampleBuffer); |
| }; |
| |
| intptr_t Profiler::Size() { |
| intptr_t size = 0; |
| if (sample_buffer_ != nullptr) { |
| size += sample_buffer_->Size(); |
| } |
| if (allocation_sample_buffer_ != nullptr) { |
| size += allocation_sample_buffer_->Size(); |
| } |
| return size; |
| } |
| |
| // A |ProcessedSample| is a combination of 1 (or more) |Sample|(s) that have |
| // been merged into a logical sample. The raw data may have been processed to |
| // improve the quality of the stack trace. |
| class ProcessedSample : public ZoneAllocated { |
| public: |
| ProcessedSample(); |
| |
| // Add |pc| to stack trace. |
| void Add(uword pc) { pcs_.Add(pc); } |
| |
| // Insert |pc| at |index|. |
| void InsertAt(intptr_t index, uword pc) { pcs_.InsertAt(index, pc); } |
| |
| // Number of pcs in stack trace. |
| intptr_t length() const { return pcs_.length(); } |
| |
| // Get |pc| at |index|. |
| uword At(intptr_t index) const { |
| ASSERT(index >= 0); |
| ASSERT(index < length()); |
| return pcs_[index]; |
| } |
| |
| // Timestamp sample was taken at. |
| int64_t timestamp() const { return timestamp_; } |
| void set_timestamp(int64_t timestamp) { timestamp_ = timestamp; } |
| |
| ThreadId tid() const { return tid_; } |
| void set_tid(ThreadId tid) { tid_ = tid; } |
| |
| // The VM tag. |
| uword vm_tag() const { return vm_tag_; } |
| void set_vm_tag(uword tag) { vm_tag_ = tag; } |
| |
| // The user tag. |
| uword user_tag() const { return user_tag_; } |
| void set_user_tag(uword tag) { user_tag_ = tag; } |
| |
| // The class id if this is an allocation profile sample. -1 otherwise. |
| intptr_t allocation_cid() const { return allocation_cid_; } |
| void set_allocation_cid(intptr_t cid) { allocation_cid_ = cid; } |
| |
| bool IsAllocationSample() const { return allocation_cid_ > 0; } |
| |
| bool is_native_allocation_sample() const { |
| return native_allocation_size_bytes_ != 0; |
| } |
| |
| uintptr_t native_allocation_size_bytes() const { |
| return native_allocation_size_bytes_; |
| } |
| void set_native_allocation_size_bytes(uintptr_t allocation_size) { |
| native_allocation_size_bytes_ = allocation_size; |
| } |
| |
| // Was the stack trace truncated? |
| bool truncated() const { return truncated_; } |
| void set_truncated(bool truncated) { truncated_ = truncated; } |
| |
| // Was the first frame in the stack trace executing? |
| bool first_frame_executing() const { return first_frame_executing_; } |
| void set_first_frame_executing(bool first_frame_executing) { |
| first_frame_executing_ = first_frame_executing; |
| } |
| |
| ProfileTrieNode* timeline_code_trie() const { return timeline_code_trie_; } |
| void set_timeline_code_trie(ProfileTrieNode* trie) { |
| ASSERT(timeline_code_trie_ == NULL); |
| timeline_code_trie_ = trie; |
| } |
| |
| ProfileTrieNode* timeline_function_trie() const { |
| return timeline_function_trie_; |
| } |
| void set_timeline_function_trie(ProfileTrieNode* trie) { |
| ASSERT(timeline_function_trie_ == NULL); |
| timeline_function_trie_ = trie; |
| } |
| |
| private: |
| void FixupCaller(const CodeLookupTable& clt, |
| uword pc_marker, |
| uword* stack_buffer); |
| |
| void CheckForMissingDartFrame(const CodeLookupTable& clt, |
| const CodeDescriptor* code, |
| uword pc_marker, |
| uword* stack_buffer); |
| |
| ZoneGrowableArray<uword> pcs_; |
| int64_t timestamp_; |
| ThreadId tid_; |
| uword vm_tag_; |
| uword user_tag_; |
| intptr_t allocation_cid_; |
| bool truncated_; |
| bool first_frame_executing_; |
| uword native_allocation_address_; |
| uintptr_t native_allocation_size_bytes_; |
| ProfileTrieNode* timeline_code_trie_; |
| ProfileTrieNode* timeline_function_trie_; |
| |
| friend class SampleBuffer; |
| DISALLOW_COPY_AND_ASSIGN(ProcessedSample); |
| }; |
| |
| // A collection of |ProcessedSample|s. |
| class ProcessedSampleBuffer : public ZoneAllocated { |
| public: |
| ProcessedSampleBuffer(); |
| |
| void Add(ProcessedSample* sample) { samples_.Add(sample); } |
| |
| intptr_t length() const { return samples_.length(); } |
| |
| ProcessedSample* At(intptr_t index) { return samples_.At(index); } |
| |
| const CodeLookupTable& code_lookup_table() const { |
| return *code_lookup_table_; |
| } |
| |
| private: |
| ZoneGrowableArray<ProcessedSample*> samples_; |
| CodeLookupTable* code_lookup_table_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProcessedSampleBuffer); |
| }; |
| |
| } // namespace dart |
| |
| #endif // RUNTIME_VM_PROFILER_H_ |