| // 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/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 SampleBlock; |
| |
| #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_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(sample_allocation_failure) |
| |
| 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 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 SampleBlockBuffer* sample_block_buffer() { |
| return sample_block_buffer_; |
| } |
| static void set_sample_block_buffer(SampleBlockBuffer* buffer) { |
| sample_block_buffer_ = buffer; |
| } |
| |
| static void DumpStackTrace(void* context); |
| static void DumpStackTrace(bool for_crash = true); |
| |
| static void SampleAllocation(Thread* thread, |
| intptr_t cid, |
| uint32_t identity_hash); |
| |
| // 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 Fuchsia, Mac and Windows 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(); |
| |
| static void ProcessCompletedBlocks(Isolate* isolate); |
| static void IsolateShutdown(Thread* thread); |
| |
| 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, |
| Sample* sample, |
| uintptr_t pc); |
| static RelaxedAtomic<bool> initialized_; |
| |
| static SampleBlockBuffer* sample_block_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; |
| |
| virtual void Reset() { visited_ = 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, |
| bool take_samples = false) |
| : port_(port), |
| thread_task_mask_(thread_task_mask), |
| time_origin_micros_(time_origin_micros), |
| time_extent_micros_(time_extent_micros), |
| take_samples_(take_samples) { |
| 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); |
| |
| bool take_samples() const { return take_samples_; } |
| |
| static constexpr intptr_t kNoTaskFilter = -1; |
| |
| private: |
| Dart_Port port_; |
| intptr_t thread_task_mask_; |
| int64_t time_origin_micros_; |
| int64_t time_extent_micros_; |
| bool take_samples_; |
| }; |
| |
| 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: |
| Sample() = default; |
| |
| void Init(Dart_Port port, int64_t timestamp, ThreadId tid) { |
| Clear(); |
| timestamp_ = timestamp; |
| tid_ = tid; |
| port_ = port; |
| next_ = nullptr; |
| } |
| |
| Dart_Port port() const { return port_; } |
| |
| // Thread sample was taken on. |
| ThreadId tid() const { return tid_; } |
| |
| void Clear() { |
| timestamp_ = 0; |
| port_ = ILLEGAL_PORT; |
| tid_ = OSThread::kInvalidThreadId; |
| for (intptr_t i = 0; i < kStackBufferSizeInWords; i++) { |
| stack_buffer_[i] = 0; |
| } |
| for (intptr_t i = 0; i < kPCArraySizeInWords; i++) { |
| pc_array_[i] = 0; |
| } |
| vm_tag_ = VMTag::kInvalidTagId; |
| user_tag_ = UserTags::kDefaultUserTag; |
| state_ = 0; |
| next_ = nullptr; |
| allocation_identity_hash_ = 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 < kPCArraySizeInWords); |
| return pc_array_[i]; |
| } |
| |
| // Set stack trace entry. |
| void SetAt(intptr_t i, uword pc) { |
| ASSERT(i >= 0); |
| ASSERT(i < kPCArraySizeInWords); |
| pc_array_[i] = pc; |
| } |
| |
| void DumpStackTrace() { |
| for (intptr_t i = 0; i < kPCArraySizeInWords; ++i) { |
| uintptr_t start = 0; |
| uword pc = At(i); |
| const char* native_symbol_name = |
| NativeSymbolResolver::LookupSymbolName(pc, &start); |
| if (native_symbol_name == nullptr) { |
| 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; } |
| |
| 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_); |
| } |
| |
| uint32_t allocation_identity_hash() const { |
| return allocation_identity_hash_; |
| } |
| |
| void set_allocation_identity_hash(uint32_t hash) { |
| allocation_identity_hash_ = hash; |
| } |
| |
| 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 SetContinuation(Sample* next) { |
| ASSERT(!is_continuation_sample()); |
| ASSERT(next_ == nullptr); |
| state_ = ContinuationSampleBit::update(true, state_); |
| next_ = next; |
| } |
| |
| Sample* continuation_sample() const { return next_; } |
| |
| 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_); } |
| |
| intptr_t metadata() const { return MetadataBits::decode(state_); } |
| void set_metadata(intptr_t metadata) { |
| state_ = MetadataBits::update(metadata, state_); |
| } |
| |
| void SetAllocationCid(intptr_t cid) { |
| set_is_allocation_sample(true); |
| set_metadata(cid); |
| } |
| |
| static constexpr int kPCArraySizeInWords = 32; |
| uword* GetPCArray() { return &pc_array_[0]; } |
| |
| static constexpr int kStackBufferSizeInWords = 2; |
| uword* GetStackBuffer() { return &stack_buffer_[0]; } |
| |
| private: |
| int64_t timestamp_; |
| Dart_Port port_; |
| ThreadId tid_; |
| uword stack_buffer_[kStackBufferSizeInWords]; |
| uword pc_array_[kPCArraySizeInWords]; |
| uword vm_tag_; |
| uword user_tag_; |
| uint32_t state_; |
| Sample* next_; |
| uint32_t allocation_identity_hash_; |
| |
| using HeadSampleBit = BitField<decltype(state_), bool, 0, 1>; |
| using LeafFrameIsDart = |
| BitField<decltype(state_), bool, HeadSampleBit::kNextBit, 1>; |
| using IgnoreBit = |
| BitField<decltype(state_), bool, LeafFrameIsDart::kNextBit, 1>; |
| using ExitFrameBit = BitField<uint32_t, bool, IgnoreBit::kNextBit, 1>; |
| using MissingFrameInsertedBit = |
| BitField<decltype(state_), bool, ExitFrameBit::kNextBit, 1>; |
| using TruncatedTraceBit = |
| BitField<decltype(state_), bool, MissingFrameInsertedBit::kNextBit, 1>; |
| using ClassAllocationSampleBit = |
| BitField<decltype(state_), bool, TruncatedTraceBit::kNextBit, 1>; |
| using ContinuationSampleBit = |
| BitField<decltype(state_), bool, ClassAllocationSampleBit::kNextBit, 1>; |
| using ThreadTaskBit = BitField<decltype(state_), |
| Thread::TaskKind, |
| ContinuationSampleBit::kNextBit, |
| 4>; |
| using MetadataBits = BitField<decltype(state_), |
| intptr_t, |
| ThreadTaskBit::kNextBit, |
| UntaggedObject::kClassIdTagSize>; |
| |
| DISALLOW_COPY_AND_ASSIGN(Sample); |
| }; |
| |
| class AbstractCode { |
| public: |
| explicit AbstractCode(ObjectPtr code) : code_(Object::Handle(code)) { |
| ASSERT(code_.IsNull() || code_.IsCode()); |
| } |
| |
| ObjectPtr ptr() const { return code_.ptr(); } |
| const Object* handle() const { return &code_; } |
| |
| uword PayloadStart() const { |
| ASSERT(code_.IsCode()); |
| return Code::Cast(code_).PayloadStart(); |
| } |
| |
| uword Size() const { |
| ASSERT(code_.IsCode()); |
| return Code::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 { |
| return ""; |
| } |
| } |
| |
| const char* QualifiedName() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).QualifiedName( |
| NameFormattingParams(Object::kUserVisibleName)); |
| } else { |
| return ""; |
| } |
| } |
| |
| bool IsStubCode() const { |
| if (code_.IsCode()) { |
| return Code::Cast(code_).IsStubCode(); |
| } 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 { |
| return Object::null(); |
| } |
| } |
| |
| bool IsNull() const { return code_.IsNull(); } |
| bool IsCode() const { return code_.IsCode(); } |
| |
| 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 != nullptr); |
| ASSERT(b != nullptr); |
| |
| 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); |
| }; |
| |
| class SampleBuffer { |
| public: |
| SampleBuffer() = default; |
| virtual ~SampleBuffer() = default; |
| |
| virtual void Init(Sample* samples, intptr_t capacity) { |
| ASSERT(samples != nullptr); |
| ASSERT(capacity > 0); |
| samples_ = samples; |
| capacity_ = capacity; |
| } |
| |
| void VisitSamples(SampleVisitor* visitor) { |
| ASSERT(visitor != nullptr); |
| 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); |
| } |
| } |
| |
| virtual Sample* ReserveSample() = 0; |
| virtual Sample* ReserveSampleAndLink(Sample* previous) = 0; |
| |
| Sample* At(intptr_t idx) const { |
| ASSERT(idx >= 0); |
| ASSERT(idx < capacity_); |
| return &samples_[idx]; |
| } |
| |
| intptr_t capacity() const { return capacity_; } |
| |
| ProcessedSampleBuffer* BuildProcessedSampleBuffer( |
| SampleFilter* filter, |
| ProcessedSampleBuffer* buffer = nullptr); |
| |
| protected: |
| Sample* Next(Sample* sample); |
| |
| ProcessedSample* BuildProcessedSample(Sample* sample, |
| const CodeLookupTable& clt); |
| |
| Sample* samples_; |
| intptr_t capacity_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SampleBuffer); |
| }; |
| |
| class SampleBlock : public SampleBuffer { |
| public: |
| // The default number of samples per block. Overridden by some tests. |
| static constexpr intptr_t kSamplesPerBlock = 100; |
| |
| SampleBlock() = default; |
| virtual ~SampleBlock() = default; |
| |
| // Returns the number of samples contained within this block. |
| intptr_t capacity() const { return capacity_; } |
| |
| Isolate* owner() const { return owner_; } |
| void set_owner(Isolate* isolate) { owner_ = isolate; } |
| |
| virtual Sample* ReserveSample(); |
| virtual Sample* ReserveSampleAndLink(Sample* previous); |
| |
| bool TryAllocateFree() { |
| State expected = kFree; |
| State desired = kSampling; |
| std::memory_order success_order = std::memory_order_acquire; |
| std::memory_order failure_order = std::memory_order_relaxed; |
| return state_.compare_exchange_strong(expected, desired, success_order, |
| failure_order); |
| } |
| bool TryAllocateCompleted() { |
| State expected = kCompleted; |
| State desired = kSampling; |
| std::memory_order success_order = std::memory_order_acquire; |
| std::memory_order failure_order = std::memory_order_relaxed; |
| if (state_.compare_exchange_strong(expected, desired, success_order, |
| failure_order)) { |
| owner_ = nullptr; |
| cursor_ = 0; |
| return true; |
| } |
| return false; |
| } |
| void MarkCompleted() { |
| ASSERT(state_.load(std::memory_order_relaxed) == kSampling); |
| state_.store(kCompleted, std::memory_order_release); |
| } |
| bool TryAcquireStreaming(Isolate* isolate) { |
| if (state_.load(std::memory_order_relaxed) != kCompleted) return false; |
| if (owner_ != isolate) return false; |
| |
| State expected = kCompleted; |
| State desired = kStreaming; |
| std::memory_order success_order = std::memory_order_acquire; |
| std::memory_order failure_order = std::memory_order_relaxed; |
| return state_.compare_exchange_strong(expected, desired, success_order, |
| failure_order); |
| } |
| void StreamingToCompleted() { |
| ASSERT(state_.load(std::memory_order_relaxed) == kStreaming); |
| state_.store(kCompleted, std::memory_order_relaxed); |
| } |
| void StreamingToFree() { |
| ASSERT(state_.load(std::memory_order_relaxed) == kStreaming); |
| owner_ = nullptr; |
| cursor_ = 0; |
| state_.store(kFree, std::memory_order_release); |
| } |
| void FreeCompleted() { |
| State expected = kCompleted; |
| State desired = kStreaming; |
| std::memory_order success_order = std::memory_order_acquire; |
| std::memory_order failure_order = std::memory_order_relaxed; |
| if (state_.compare_exchange_strong(expected, desired, success_order, |
| failure_order)) { |
| StreamingToFree(); |
| } |
| } |
| |
| protected: |
| bool HasStreamableSamples(const GrowableObjectArray& tag_table, UserTag* tag); |
| |
| enum State : uint32_t { |
| kFree, |
| kSampling, // I.e., writing. |
| kCompleted, |
| kStreaming, // I.e., reading. |
| }; |
| std::atomic<State> state_ = kFree; |
| RelaxedAtomic<uint32_t> cursor_ = 0; |
| Isolate* owner_ = nullptr; |
| |
| private: |
| friend class SampleBlockListProcessor; |
| friend class SampleBlockBuffer; |
| |
| DISALLOW_COPY_AND_ASSIGN(SampleBlock); |
| }; |
| |
| class SampleBlockBuffer { |
| public: |
| static constexpr intptr_t kDefaultBlockCount = 600; |
| |
| // Creates a SampleBlockBuffer with a predetermined number of blocks. |
| // |
| // Defaults to kDefaultBlockCount blocks. Block size is fixed to |
| // SampleBlock::kSamplesPerBlock samples per block, except for in tests. |
| explicit SampleBlockBuffer( |
| intptr_t blocks = kDefaultBlockCount, |
| intptr_t samples_per_block = SampleBlock::kSamplesPerBlock); |
| |
| virtual ~SampleBlockBuffer(); |
| |
| void VisitSamples(SampleVisitor* visitor) { |
| ASSERT(visitor != nullptr); |
| for (intptr_t i = 0; i < capacity_; ++i) { |
| blocks_[i].VisitSamples(visitor); |
| } |
| } |
| |
| void FreeCompletedBlocks(); |
| |
| // Reserves a sample for a CPU profile. |
| // |
| // Returns nullptr when a sample can't be reserved. |
| Sample* ReserveCPUSample(Isolate* isolate); |
| |
| // Reserves a sample for a Dart object allocation profile. |
| // |
| // Returns nullptr when a sample can't be reserved. |
| Sample* ReserveAllocationSample(Isolate* isolate); |
| |
| intptr_t Size() const { return memory_->size(); } |
| |
| ProcessedSampleBuffer* BuildProcessedSampleBuffer( |
| Isolate* isolate, |
| SampleFilter* filter, |
| ProcessedSampleBuffer* buffer = nullptr); |
| |
| private: |
| Sample* ReserveSampleImpl(Isolate* isolate, bool allocation_sample); |
| |
| // Returns nullptr if there are no available blocks. |
| SampleBlock* ReserveSampleBlock(); |
| |
| // Sample block management. |
| RelaxedAtomic<int> cursor_; |
| SampleBlock* blocks_; |
| intptr_t capacity_; |
| |
| // Sample buffer management. |
| VirtualMemory* memory_; |
| Sample* sample_buffer_; |
| |
| friend class Isolate; |
| DISALLOW_COPY_AND_ASSIGN(SampleBlockBuffer); |
| }; |
| |
| intptr_t Profiler::Size() { |
| intptr_t size = 0; |
| if (sample_block_buffer_ != nullptr) { |
| size += sample_block_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; } |
| |
| // The identity hash code of the allocated object if this is an allocation |
| // profile sample. -1 otherwise. |
| uint32_t allocation_identity_hash() const { |
| return allocation_identity_hash_; |
| } |
| void set_allocation_identity_hash(uint32_t hash) { |
| allocation_identity_hash_ = hash; |
| } |
| |
| bool IsAllocationSample() const { return allocation_cid_ > 0; } |
| |
| // 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; |
| } |
| |
| 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_; |
| uint32_t allocation_identity_hash_; |
| bool truncated_; |
| bool first_frame_executing_; |
| |
| 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); |
| }; |
| |
| class SampleBlockProcessor : public AllStatic { |
| public: |
| static void Init(); |
| |
| static void Startup(); |
| static void Cleanup(); |
| |
| private: |
| static constexpr intptr_t kMaxThreads = 4096; |
| static bool initialized_; |
| static bool shutdown_; |
| static bool thread_running_; |
| static ThreadJoinId processor_thread_id_; |
| static Monitor* monitor_; |
| |
| static void ThreadMain(uword parameters); |
| }; |
| |
| } // namespace dart |
| |
| #endif // RUNTIME_VM_PROFILER_H_ |