blob: ff8e14853d20a9caff4f295efa730c498b5988c8 [file] [log] [blame]
// 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 VM_PROFILER_H_
#define VM_PROFILER_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/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 SampleBuffer;
class Profiler : public AllStatic {
public:
static void InitOnce();
static void Shutdown();
static void SetSampleDepth(intptr_t depth);
static void SetSamplePeriod(intptr_t period);
static void InitProfilingForIsolate(Isolate* isolate,
bool shared_buffer = true);
static void ShutdownProfilingForIsolate(Isolate* isolate);
static void BeginExecution(Isolate* isolate);
static void EndExecution(Isolate* isolate);
static SampleBuffer* sample_buffer() {
return sample_buffer_;
}
static void RecordAllocation(Thread* thread, intptr_t cid);
private:
static bool initialized_;
static Monitor* monitor_;
static void RecordSampleInterruptCallback(const InterruptedThreadState& state,
void* data);
static SampleBuffer* sample_buffer_;
};
class IsolateProfilerData {
public:
IsolateProfilerData(SampleBuffer* sample_buffer, bool own_sample_buffer);
~IsolateProfilerData();
SampleBuffer* sample_buffer() const { return sample_buffer_; }
void set_sample_buffer(SampleBuffer* sample_buffer) {
sample_buffer_ = sample_buffer;
}
bool blocked() const {
return block_count_ > 0;
}
void Block();
void Unblock();
private:
SampleBuffer* sample_buffer_;
bool own_sample_buffer_;
intptr_t block_count_;
DISALLOW_COPY_AND_ASSIGN(IsolateProfilerData);
};
class SampleVisitor : public ValueObject {
public:
explicit SampleVisitor(Isolate* isolate) : isolate_(isolate), visited_(0) { }
virtual ~SampleVisitor() {}
virtual void VisitSample(Sample* sample) = 0;
intptr_t visited() const {
return visited_;
}
void IncrementVisited() {
visited_++;
}
Isolate* isolate() const {
return isolate_;
}
private:
Isolate* isolate_;
intptr_t visited_;
DISALLOW_IMPLICIT_CONSTRUCTORS(SampleVisitor);
};
class SampleFilter : public ValueObject {
public:
explicit SampleFilter(Isolate* isolate) : isolate_(isolate) { }
virtual ~SampleFilter() { }
// Override this function.
// Return |true| if |sample| passes the filter.
virtual bool FilterSample(Sample* sample) {
return true;
}
Isolate* isolate() const {
return isolate_;
}
private:
Isolate* isolate_;
};
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(Isolate* isolate, int64_t timestamp, ThreadId tid) {
Clear();
timestamp_ = timestamp;
tid_ = tid;
isolate_ = isolate;
}
// Isolate sample was taken from.
Isolate* isolate() const {
return isolate_;
}
// Thread sample was taken on.
ThreadId tid() const {
return tid_;
}
void Clear() {
isolate_ = NULL;
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;
continuation_index_ = -1;
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;
}
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_);
}
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 InitOnce();
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,
};
class HeadSampleBit : public BitField<bool, kHeadSampleBit, 1> {};
class LeafFrameIsDart : public BitField<bool, kLeafFrameIsDartBit, 1> {};
class IgnoreBit : public BitField<bool, kIgnoreBit, 1> {};
class ExitFrameBit : public BitField<bool, kExitFrameBit, 1> {};
class MissingFrameInsertedBit
: public BitField<bool, kMissingFrameInsertedBit, 1> {};
class TruncatedTraceBit : public BitField<bool, kTruncatedTraceBit, 1> {};
class ClassAllocationSampleBit
: public BitField<bool, kClassAllocationSampleBit, 1> {};
class ContinuationSampleBit
: public BitField<bool, kContinuationSampleBit, 1> {};
int64_t timestamp_;
ThreadId tid_;
Isolate* isolate_;
uword pc_marker_;
uword stack_buffer_[kStackBufferSizeInWords];
uword vm_tag_;
uword user_tag_;
uword metadata_;
uword lr_;
uword state_;
intptr_t continuation_index_;
/* There are a variable number of words that follow, the words hold the
* sampled pc values. Access via GetPCArray() */
DISALLOW_COPY_AND_ASSIGN(Sample);
};
// Ring buffer of Samples that is (usually) shared by many isolates.
class SampleBuffer {
public:
static const intptr_t kDefaultBufferCapacity = 120000; // 2 minutes @ 1000hz.
explicit SampleBuffer(intptr_t capacity = kDefaultBufferCapacity);
~SampleBuffer() {
if (samples_ != NULL) {
free(samples_);
samples_ = NULL;
cursor_ = 0;
capacity_ = 0;
}
}
intptr_t capacity() const { return capacity_; }
Sample* At(intptr_t idx) const;
intptr_t ReserveSampleSlot();
Sample* ReserveSample();
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->isolate() != visitor->isolate()) {
// 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);
private:
ProcessedSample* BuildProcessedSample(Sample* sample);
Sample* Next(Sample* sample);
Sample* samples_;
intptr_t capacity_;
uintptr_t cursor_;
DISALLOW_COPY_AND_ASSIGN(SampleBuffer);
};
// 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; }
// 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;
}
// 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(Thread* thread,
Isolate* vm_isolate,
uword pc_marker,
uword* stack_buffer);
void CheckForMissingDartFrame(Isolate* isolate,
Isolate* vm_isolate,
const Code& code,
uword pc_marker,
uword* stack_buffer);
static RawCode* FindCodeForPC(Isolate* isolate,
Isolate* vm_isolate,
uword pc);
static bool ContainedInDartCodeHeaps(Isolate* isolate,
Isolate* vm_isolate,
uword pc);
ZoneGrowableArray<uword> pcs_;
int64_t timestamp_;
uword vm_tag_;
uword user_tag_;
intptr_t allocation_cid_;
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);
}
private:
ZoneGrowableArray<ProcessedSample*> samples_;
DISALLOW_COPY_AND_ASSIGN(ProcessedSampleBuffer);
};
} // namespace dart
#endif // VM_PROFILER_H_