mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Ben Konyi](/assets/img/avatar_default.png)
Building an empty CPU profile is still costly as function tables are populated whether or not there's samples in the profile. This change checks to see if any sample in the list of SampleBlocks was collected under a UserTag with streaming enabled before building the profile. TEST=CQ, manual testing Change-Id: Ib526f9999a8b2bf48a7df1488c7dcf959031cf5b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238183 Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
1088 lines
31 KiB
C++
1088 lines
31 KiB
C++
// 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 SampleBlock;
|
|
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) \
|
|
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 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 SampleBlockBuffer* sample_block_buffer() {
|
|
return sample_block_buffer_;
|
|
}
|
|
static void set_sample_block_buffer(SampleBlockBuffer* buffer) {
|
|
sample_block_buffer_ = 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,
|
|
uint32_t identity_hash);
|
|
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,
|
|
Sample* sample,
|
|
uintptr_t pc);
|
|
static RelaxedAtomic<bool> initialized_;
|
|
|
|
static SampleBlockBuffer* sample_block_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;
|
|
|
|
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)
|
|
: 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:
|
|
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;
|
|
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
|
|
native_allocation_address_ = 0;
|
|
native_allocation_size_bytes_ = 0;
|
|
next_free_ = NULL;
|
|
#endif
|
|
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);
|
|
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; }
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
|
|
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; }
|
|
#else
|
|
uword native_allocation_address() const { return 0; }
|
|
void set_native_allocation_address(uword address) { UNREACHABLE(); }
|
|
|
|
uintptr_t native_allocation_size_bytes() const { return 0; }
|
|
void set_native_allocation_size_bytes(uintptr_t size) { UNREACHABLE(); }
|
|
|
|
Sample* next_free() const { return nullptr; }
|
|
void set_next_free(Sample* next_free) { UNREACHABLE(); }
|
|
#endif // defined(DART_USE_TCMALLOC) && defined(DEBUG)
|
|
|
|
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:
|
|
enum StateBits {
|
|
kHeadSampleBit = 0,
|
|
kLeafFrameIsDartBit = 1,
|
|
kIgnoreBit = 2,
|
|
kExitFrameBit = 3,
|
|
kMissingFrameInsertedBit = 4,
|
|
kTruncatedTraceBit = 5,
|
|
kClassAllocationSampleBit = 6,
|
|
kContinuationSampleBit = 7,
|
|
kThreadTaskBit = 8, // 7 bits.
|
|
kMetadataBit = 15, // 16 bits.
|
|
kNextFreeBit = 31,
|
|
};
|
|
class HeadSampleBit : public BitField<uint32_t, bool, kHeadSampleBit, 1> {};
|
|
class LeafFrameIsDart
|
|
: public BitField<uint32_t, bool, kLeafFrameIsDartBit, 1> {};
|
|
class IgnoreBit : public BitField<uint32_t, bool, kIgnoreBit, 1> {};
|
|
class ExitFrameBit : public BitField<uint32_t, bool, kExitFrameBit, 1> {};
|
|
class MissingFrameInsertedBit
|
|
: public BitField<uint32_t, bool, kMissingFrameInsertedBit, 1> {};
|
|
class TruncatedTraceBit
|
|
: public BitField<uint32_t, bool, kTruncatedTraceBit, 1> {};
|
|
class ClassAllocationSampleBit
|
|
: public BitField<uint32_t, bool, kClassAllocationSampleBit, 1> {};
|
|
class ContinuationSampleBit
|
|
: public BitField<uint32_t, bool, kContinuationSampleBit, 1> {};
|
|
class ThreadTaskBit
|
|
: public BitField<uint32_t, Thread::TaskKind, kThreadTaskBit, 7> {};
|
|
class MetadataBits : public BitField<uint32_t, intptr_t, kMetadataBit, 16> {};
|
|
|
|
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_;
|
|
|
|
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
|
|
uword native_allocation_address_;
|
|
uintptr_t native_allocation_size_bytes_;
|
|
Sample* next_free_;
|
|
#endif
|
|
|
|
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());
|
|
}
|
|
|
|
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 != 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);
|
|
};
|
|
|
|
// Interface for a class that can create a ProcessedSampleBuffer.
|
|
class ProcessedSampleBufferBuilder {
|
|
public:
|
|
virtual ~ProcessedSampleBufferBuilder() = default;
|
|
virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
|
|
SampleFilter* filter,
|
|
ProcessedSampleBuffer* buffer = nullptr) = 0;
|
|
};
|
|
|
|
class SampleBuffer : public ProcessedSampleBufferBuilder {
|
|
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 != 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);
|
|
}
|
|
}
|
|
|
|
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_; }
|
|
|
|
virtual 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 const intptr_t kSamplesPerBlock = 100;
|
|
|
|
SampleBlock() = default;
|
|
virtual ~SampleBlock() = default;
|
|
|
|
void Clear() {
|
|
allocation_block_ = false;
|
|
cursor_ = 0;
|
|
full_ = false;
|
|
evictable_ = false;
|
|
next_free_ = nullptr;
|
|
}
|
|
|
|
// Returns the number of samples contained within this block.
|
|
intptr_t capacity() const { return capacity_; }
|
|
|
|
// Specify whether or not this block is used for assigning allocation
|
|
// samples.
|
|
void set_is_allocation_block(bool is_allocation_block) {
|
|
allocation_block_ = is_allocation_block;
|
|
}
|
|
|
|
Isolate* owner() const { return owner_; }
|
|
void set_owner(Isolate* isolate) { owner_ = isolate; }
|
|
|
|
// Manually marks the block as full so it can be processed and added back to
|
|
// the pool of available blocks.
|
|
void release_block() { full_.store(true); }
|
|
|
|
// When true, this sample block is considered complete and will no longer be
|
|
// used to assign new Samples. This block is **not** available for
|
|
// re-allocation simply because it's full. It must be processed by
|
|
// SampleBlockBuffer::ProcessCompletedBlocks before it can be considered
|
|
// evictable and available for re-allocation.
|
|
bool is_full() const { return full_.load(); }
|
|
|
|
// When true, this sample block is available for re-allocation.
|
|
bool evictable() const { return evictable_.load(); }
|
|
|
|
virtual Sample* ReserveSample();
|
|
virtual Sample* ReserveSampleAndLink(Sample* previous);
|
|
|
|
protected:
|
|
bool HasStreamableSamples(const GrowableObjectArray& tag_table, UserTag* tag);
|
|
|
|
Isolate* owner_ = nullptr;
|
|
bool allocation_block_ = false;
|
|
|
|
intptr_t index_;
|
|
RelaxedAtomic<int> cursor_ = 0;
|
|
RelaxedAtomic<bool> full_ = false;
|
|
RelaxedAtomic<bool> evictable_ = false;
|
|
|
|
SampleBlock* next_free_ = nullptr;
|
|
|
|
private:
|
|
friend class SampleBlockListProcessor;
|
|
friend class SampleBlockBuffer;
|
|
friend class Isolate;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SampleBlock);
|
|
};
|
|
|
|
class SampleBlockBuffer : public ProcessedSampleBufferBuilder {
|
|
public:
|
|
static const 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 != NULL);
|
|
for (intptr_t i = 0; i < cursor_.load(); ++i) {
|
|
(&blocks_[i])->VisitSamples(visitor);
|
|
}
|
|
}
|
|
|
|
// Returns true when there is at least a single block that needs to be
|
|
// processed.
|
|
//
|
|
// NOTE: this should only be called from the interrupt handler as
|
|
// invocation will have the side effect of clearing the underlying flag.
|
|
bool process_blocks() { return can_process_block_.exchange(false); }
|
|
|
|
// Iterates over the blocks in the buffer and processes blocks marked as
|
|
// full. Processing consists of sending a service event with the samples from
|
|
// completed, unprocessed blocks and marking these blocks are evictable
|
|
// (i.e., safe to be re-allocated and re-used).
|
|
void ProcessCompletedBlocks();
|
|
|
|
// 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(); }
|
|
|
|
virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
|
|
SampleFilter* filter,
|
|
ProcessedSampleBuffer* buffer = nullptr);
|
|
|
|
private:
|
|
Sample* ReserveSampleImpl(Isolate* isolate, bool allocation_sample);
|
|
|
|
// Returns nullptr if there are no available blocks.
|
|
SampleBlock* ReserveSampleBlock();
|
|
|
|
void FreeBlock(SampleBlock* block) {
|
|
ASSERT(block->next_free_ == nullptr);
|
|
MutexLocker ml(&free_block_lock_);
|
|
if (free_list_head_ == nullptr) {
|
|
free_list_head_ = block;
|
|
free_list_tail_ = block;
|
|
return;
|
|
}
|
|
free_list_tail_->next_free_ = block;
|
|
free_list_tail_ = block;
|
|
}
|
|
|
|
SampleBlock* GetFreeBlock() {
|
|
MutexLocker ml(&free_block_lock_);
|
|
if (free_list_head_ == nullptr) {
|
|
return nullptr;
|
|
}
|
|
SampleBlock* block = free_list_head_;
|
|
free_list_head_ = block->next_free_;
|
|
if (free_list_head_ == nullptr) {
|
|
free_list_tail_ = nullptr;
|
|
}
|
|
block->next_free_ = nullptr;
|
|
return block;
|
|
}
|
|
|
|
Mutex free_block_lock_;
|
|
RelaxedAtomic<bool> can_process_block_ = false;
|
|
|
|
// Sample block management.
|
|
RelaxedAtomic<int> cursor_;
|
|
SampleBlock* blocks_;
|
|
intptr_t capacity_;
|
|
SampleBlock* free_list_head_;
|
|
SampleBlock* free_list_tail_;
|
|
|
|
// Sample buffer management.
|
|
VirtualMemory* memory_;
|
|
Sample* sample_buffer_;
|
|
|
|
friend class Isolate;
|
|
DISALLOW_COPY_AND_ASSIGN(SampleBlockBuffer);
|
|
};
|
|
|
|
class SampleBlockListProcessor : public ProcessedSampleBufferBuilder {
|
|
public:
|
|
explicit SampleBlockListProcessor(SampleBlock* head) : head_(head) {}
|
|
|
|
virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
|
|
SampleFilter* filter,
|
|
ProcessedSampleBuffer* buffer = nullptr);
|
|
|
|
// Returns true when at least one sample in the sample block list has a user
|
|
// tag with CPU sample streaming enabled.
|
|
bool HasStreamableSamples(Thread* thread);
|
|
|
|
private:
|
|
SampleBlock* head_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SampleBlockListProcessor);
|
|
};
|
|
|
|
class AllocationSampleBuffer : public SampleBuffer {
|
|
public:
|
|
explicit AllocationSampleBuffer(intptr_t capacity = 60000);
|
|
virtual ~AllocationSampleBuffer();
|
|
|
|
virtual Sample* ReserveSample();
|
|
virtual Sample* ReserveSampleAndLink(Sample* previous);
|
|
void FreeAllocationSample(Sample* sample);
|
|
|
|
intptr_t Size() { return memory_->size(); }
|
|
|
|
private:
|
|
intptr_t ReserveSampleSlotLocked();
|
|
|
|
Mutex mutex_;
|
|
Sample* free_sample_list_;
|
|
VirtualMemory* memory_;
|
|
RelaxedAtomic<int> cursor_ = 0;
|
|
DISALLOW_COPY_AND_ASSIGN(AllocationSampleBuffer);
|
|
};
|
|
|
|
intptr_t Profiler::Size() {
|
|
intptr_t size = 0;
|
|
if (sample_block_buffer_ != nullptr) {
|
|
size += sample_block_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; }
|
|
|
|
// 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; }
|
|
|
|
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_;
|
|
uint32_t allocation_identity_hash_;
|
|
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);
|
|
};
|
|
|
|
class SampleBlockProcessor : public AllStatic {
|
|
public:
|
|
static void Init();
|
|
|
|
static void Startup();
|
|
static void Cleanup();
|
|
|
|
private:
|
|
static const 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_
|