blob: 465b1f56523880c8c2282b91715f53b3dbf8513b [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_PROFILER_SERVICE_H_
#define RUNTIME_VM_PROFILER_SERVICE_H_
#include "platform/text_buffer.h"
#include "vm/allocation.h"
#include "vm/code_observers.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/object.h"
#include "vm/profiler.h"
#include "vm/tags.h"
#include "vm/thread_interrupter.h"
#include "vm/token_position.h"
#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "vm/protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "vm/protos/perfetto/trace/trace_packet.pbzero.h"
#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
// CPU Profile model and service protocol bits.
// NOTE: For sampling and stack walking related code, see profiler.h.
namespace dart {
// Forward declarations.
class Code;
class Function;
class JSONArray;
class JSONStream;
class ProfileFunctionTable;
class ProfileCodeTable;
class SampleFilter;
class ProcessedSample;
class ProcessedSampleBuffer;
class Profile;
class ProfileFunctionSourcePosition {
public:
explicit ProfileFunctionSourcePosition(TokenPosition token_pos);
void Tick(bool exclusive);
TokenPosition token_pos() const { return token_pos_; }
intptr_t exclusive_ticks() const { return exclusive_ticks_; }
intptr_t inclusive_ticks() const { return inclusive_ticks_; }
private:
TokenPosition token_pos_;
intptr_t exclusive_ticks_;
intptr_t inclusive_ticks_;
DISALLOW_ALLOCATION();
};
class ProfileCodeInlinedFunctionsCache : public ZoneAllocated {
public:
ProfileCodeInlinedFunctionsCache() : cache_cursor_(0), last_hit_(0) {
for (intptr_t i = 0; i < kCacheSize; i++) {
cache_[i].Reset();
}
cache_hit_ = 0;
cache_miss_ = 0;
}
~ProfileCodeInlinedFunctionsCache() {
if (FLAG_trace_profiler) {
intptr_t total = cache_hit_ + cache_miss_;
OS::PrintErr("LOOKUPS: %" Pd " HITS: %" Pd " MISSES: %" Pd "\n", total,
cache_hit_, cache_miss_);
}
}
void Get(uword pc,
const Code& code,
ProcessedSample* sample,
intptr_t frame_index,
// Outputs:
GrowableArray<const Function*>** inlined_functions,
GrowableArray<TokenPosition>** inlined_token_positions,
TokenPosition* token_position);
private:
bool FindInCache(uword pc,
intptr_t offset,
GrowableArray<const Function*>** inlined_functions,
GrowableArray<TokenPosition>** inlined_token_positions,
TokenPosition* token_position);
// Add to cache and fill in outputs.
void Add(uword pc,
const Code& code,
ProcessedSample* sample,
intptr_t frame_index,
// Outputs:
GrowableArray<const Function*>** inlined_functions,
GrowableArray<TokenPosition>** inlined_token_positions,
TokenPosition* token_position);
intptr_t NextFreeIndex() {
cache_cursor_ = (cache_cursor_ + 1) % kCacheSize;
return cache_cursor_;
}
intptr_t OffsetForPC(uword pc,
const Code& code,
ProcessedSample* sample,
intptr_t frame_index);
struct CacheEntry {
void Reset() {
pc = 0;
offset = 0;
inlined_functions.Clear();
inlined_token_positions.Clear();
}
uword pc;
intptr_t offset;
GrowableArray<const Function*> inlined_functions;
GrowableArray<TokenPosition> inlined_token_positions;
TokenPosition token_position = TokenPosition::kNoSource;
};
static constexpr intptr_t kCacheSize = 128;
intptr_t cache_cursor_;
intptr_t last_hit_;
CacheEntry cache_[kCacheSize];
intptr_t cache_miss_;
intptr_t cache_hit_;
};
// Profile data related to a |Function|.
class ProfileFunction : public ZoneAllocated {
public:
enum Kind {
kDartFunction, // Dart function.
kNativeFunction, // Synthetic function for Native (C/C++).
kTagFunction, // Synthetic function for a VM or User tag.
kStubFunction, // Synthetic function for stub code.
kUnknownFunction, // A singleton function for unknown objects.
};
ProfileFunction(Kind kind,
const char* name,
const Function& function,
const intptr_t table_index);
const char* name() const {
ASSERT(name_ != nullptr);
return name_;
}
const char* Name() const;
const Function* function() const { return &function_; }
// Returns the resolved_url for the script containing this function.
const char* ResolvedScriptUrl() const;
bool is_visible() const;
intptr_t table_index() const { return table_index_; }
Kind kind() const { return kind_; }
intptr_t exclusive_ticks() const { return exclusive_ticks_; }
intptr_t inclusive_ticks() const { return inclusive_ticks_; }
void IncInclusiveTicks() { inclusive_ticks_++; }
void Tick(bool exclusive,
intptr_t inclusive_serial,
TokenPosition token_position);
static const char* KindToCString(Kind kind);
void PrintToJSONArray(JSONArray* functions, bool print_only_ids = false);
// Returns true if the call was successful and |pfsp| is set.
bool GetSinglePosition(ProfileFunctionSourcePosition* pfsp);
void TickSourcePosition(TokenPosition token_position, bool exclusive);
intptr_t NumSourcePositions() const {
return source_position_ticks_.length();
}
const ProfileFunctionSourcePosition& GetSourcePosition(intptr_t i) const {
return source_position_ticks_.At(i);
}
private:
const Kind kind_;
const char* name_;
const Function& function_;
const intptr_t table_index_;
ZoneGrowableArray<intptr_t> profile_codes_;
ZoneGrowableArray<ProfileFunctionSourcePosition> source_position_ticks_;
intptr_t exclusive_ticks_;
intptr_t inclusive_ticks_;
intptr_t inclusive_serial_;
void PrintToJSONObject(JSONObject* func);
// A |ProfileCode| that contains this function.
void AddProfileCode(intptr_t code_table_index);
friend class ProfileCode;
friend class ProfileBuilder;
};
class ProfileCodeAddress {
public:
explicit ProfileCodeAddress(uword pc);
void Tick(bool exclusive);
uword pc() const { return pc_; }
intptr_t exclusive_ticks() const { return exclusive_ticks_; }
intptr_t inclusive_ticks() const { return inclusive_ticks_; }
private:
uword pc_;
intptr_t exclusive_ticks_;
intptr_t inclusive_ticks_;
};
// Profile data related to a |Code|.
class ProfileCode : public ZoneAllocated {
public:
enum Kind {
kDartCode, // Live Dart code.
kCollectedCode, // Dead Dart code.
kNativeCode, // Native code.
kReusedCode, // Dead Dart code that has been reused by new kDartCode.
kTagCode, // A special kind of code representing a tag.
};
ProfileCode(Kind kind,
uword start,
uword end,
int64_t timestamp,
const AbstractCode code);
Kind kind() const { return kind_; }
uword start() const { return start_; }
void set_start(uword start) { start_ = start; }
uword end() const { return end_; }
void set_end(uword end) { end_ = end; }
void ExpandLower(uword start);
void ExpandUpper(uword end);
void TruncateLower(uword start);
void TruncateUpper(uword end);
bool Contains(uword pc) const { return (pc >= start_) && (pc < end_); }
bool Overlaps(const ProfileCode* other) const;
int64_t compile_timestamp() const { return compile_timestamp_; }
void set_compile_timestamp(int64_t timestamp) {
compile_timestamp_ = timestamp;
}
intptr_t exclusive_ticks() const { return exclusive_ticks_; }
void set_exclusive_ticks(intptr_t exclusive_ticks) {
exclusive_ticks_ = exclusive_ticks;
}
void IncExclusiveTicks() { exclusive_ticks_++; }
intptr_t inclusive_ticks() const { return inclusive_ticks_; }
void set_inclusive_ticks(intptr_t inclusive_ticks) {
inclusive_ticks_ = inclusive_ticks;
}
void IncInclusiveTicks() { inclusive_ticks_++; }
bool IsOptimizedDart() const;
const AbstractCode code() const { return code_; }
const char* name() const { return name_; }
void SetName(const char* name);
void GenerateAndSetSymbolName(const char* prefix);
static const char* KindToCString(Kind kind);
void PrintToJSONArray(JSONArray* codes);
ProfileFunction* function() const { return function_; }
intptr_t code_table_index() const { return code_table_index_; }
private:
void Tick(uword pc, bool exclusive, intptr_t serial);
void TickAddress(uword pc, bool exclusive);
ProfileFunction* SetFunctionAndName(ProfileFunctionTable* table);
void PrintNativeCode(JSONObject* profile_code_obj);
void PrintCollectedCode(JSONObject* profile_code_obj);
void PrintOverwrittenCode(JSONObject* profile_code_obj);
void PrintTagCode(JSONObject* profile_code_obj);
void set_code_table_index(intptr_t index) { code_table_index_ = index; }
const Kind kind_;
uword start_;
uword end_;
intptr_t exclusive_ticks_;
intptr_t inclusive_ticks_;
intptr_t inclusive_serial_;
const AbstractCode code_;
char* name_;
int64_t compile_timestamp_;
ProfileFunction* function_;
intptr_t code_table_index_;
ZoneGrowableArray<ProfileCodeAddress> address_ticks_;
friend class ProfileBuilder;
};
class ProfileCodeTable : public ZoneAllocated {
public:
ProfileCodeTable() : table_(8) {}
intptr_t length() const { return table_.length(); }
ProfileCode* At(intptr_t index) const {
ASSERT(index >= 0);
ASSERT(index < length());
return table_[index];
}
// Find the table index to the ProfileCode containing pc.
// Returns < 0 if not found.
intptr_t FindCodeIndexForPC(uword pc) const;
ProfileCode* FindCodeForPC(uword pc) const {
intptr_t index = FindCodeIndexForPC(pc);
if (index < 0) {
return nullptr;
}
return At(index);
}
// Insert |new_code| into the table. Returns the table index where |new_code|
// was inserted. Will merge with an overlapping ProfileCode if one is present.
intptr_t InsertCode(ProfileCode* new_code);
private:
void FindNeighbors(uword pc,
intptr_t* lo,
intptr_t* hi,
ProfileCode** lo_code,
ProfileCode** hi_code) const;
void VerifyOrder();
void VerifyOverlap();
ZoneGrowableArray<ProfileCode*> table_;
};
// The model for a profile. Most of the model is zone allocated, therefore
// a zone must be created that lives longer than this object.
class Profile : public ValueObject {
public:
Profile();
// Build a filtered model using |filter|.
void Build(Thread* thread,
Isolate* isolate,
SampleFilter* filter,
SampleBlockBuffer* sample_block_buffer);
// After building:
int64_t min_time() const { return min_time_; }
int64_t max_time() const { return max_time_; }
int64_t GetTimeSpan() const { return max_time() - min_time(); }
intptr_t sample_count() const { return sample_count_; }
ProcessedSample* SampleAt(intptr_t index);
intptr_t NumFunctions() const;
ProfileFunction* GetFunction(intptr_t index);
ProfileCode* GetCode(intptr_t index);
ProfileCode* GetCodeFromPC(uword pc, int64_t timestamp);
void PrintProfileJSON(JSONStream* stream, bool include_code_samples);
void PrintProfileJSON(JSONObject* obj,
bool include_code_samples,
bool is_event = false);
#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
void PrintProfilePerfetto(JSONStream* js);
#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
ProfileFunction* FindFunction(const Function& function);
private:
void PrintHeaderJSON(JSONObject* obj);
void ProcessSampleFrameJSON(JSONArray* stack,
ProfileCodeInlinedFunctionsCache* cache,
ProcessedSample* sample,
intptr_t frame_index);
#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
void ProcessSampleFramePerfetto(
perfetto::protos::pbzero::Callstack* callstack,
ProfileCodeInlinedFunctionsCache* cache,
ProcessedSample* sample,
intptr_t frame_index);
#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
void ProcessInlinedFunctionFrameJSON(JSONArray* stack,
const Function* inlined_function);
void PrintFunctionFrameIndexJSON(JSONArray* stack, ProfileFunction* function);
void PrintCodeFrameIndexJSON(JSONArray* stack,
ProcessedSample* sample,
intptr_t frame_index);
void PrintSamplesJSON(JSONObject* obj, bool code_samples);
#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
/*
* Appends Perfetto packets describing the CPU samples in this profile to
* |jsonBase64String|. The |packet| parameter allows us to reuse an existing
* heap-buffered packet to avoid allocating a new one.
*/
void PrintSamplesPerfetto(
JSONBase64String* jsonBase64String,
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet);
#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
Zone* zone_;
ProcessedSampleBuffer* samples_;
ProfileCodeTable* live_code_;
ProfileCodeTable* dead_code_;
ProfileCodeTable* tag_code_;
ProfileFunctionTable* functions_;
intptr_t dead_code_index_offset_;
intptr_t tag_code_index_offset_;
int64_t min_time_;
int64_t max_time_;
intptr_t sample_count_;
friend class ProfileBuilder;
};
class ProfilerService : public AllStatic {
public:
/*
* Prints a CpuSamples service response into |js|.
*/
static void PrintJSON(JSONStream* stream,
int64_t time_origin_micros,
int64_t time_extent_micros,
bool include_code_samples);
#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
/*
* Prints a PerfettoCpuSamples service response into |js|.
*/
static void PrintPerfetto(JSONStream* js,
int64_t time_origin_micros,
int64_t time_extent_micros);
#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT)
static void PrintAllocationJSON(JSONStream* stream,
const Class& cls,
int64_t time_origin_micros,
int64_t time_extent_micros);
static void PrintAllocationJSON(JSONStream* stream,
int64_t time_origin_micros,
int64_t time_extent_micros);
static void ClearSamples();
private:
enum PrintFormat : bool { JSON = false, Perfetto = true };
static void PrintCommonImpl(PrintFormat format,
Thread* thread,
JSONStream* js,
SampleFilter* filter,
SampleBlockBuffer* buffer,
bool include_code_samples);
/*
* If |format| is |PrintFormat::JSON|, prints a CpuSamples service response
* into |js|. If |format| is |PrintFormat::Perfetto|, prints a
* PerfettoCpuSamples service response into |js|. Note that the value of
* |include_code_samples| is ignored when |format| is |PrintFormat::Perfetto|.
*/
static void PrintCommon(PrintFormat format,
JSONStream* js,
int64_t time_origin_micros,
int64_t time_extent_micros,
bool include_code_samples = false);
};
} // namespace dart
#endif // RUNTIME_VM_PROFILER_SERVICE_H_