dart-sdk/runtime/vm/profiler_service.h

455 lines
13 KiB
C
Raw Normal View History

// 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"
// 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;
[vm] Remove non-private uses of TokenPosition::value(). Instead, split each old use into the following cases: * If the TokenPosition value is expected to be a real token position, then use TokenPosition::Pos(). * If the TokenPosition is being serialized in some way, then use TokenPosition::Serialize() and change the place where the TokenPosition is recreated to use TokenPosition::Deserialize(). * If the value of the TokenPosition is being printed for debugging purposes, then just use TokenPosition::ToCString() instead. That is, we try to pin down when token positions are expected to be real vs. when other types of token positions can be found. Another source of possible error when using token positions is to convert between synthetic and real token positions. In the past, synthetic token positions may have been based off real token positions, but that is no longer the case. Thus, all methods that allow that conversion have been removed, and instead there is a new static method for constructing synthetic tokens from valid nonces. This CL also makes it so that Pos() and relational operators on token positions are only defined on real token positions, to avoid any assumptions about what the value encoded in synthetic positions mean. To help with cases where non-real token positions may occur, four helper methods are added: * TokenPosition::Min(a, b): A static method that returns the smallest real token position provided. If neither `a` or `b` are real, returns `a`. * TokenPosition::Max(a, b): A static method that returns the largest real token position provided. If neither `a` or `b` are real, returns `a`. * TokenPosition::IsWithin(start, end): Determines whether `this` falls between `start` and `end` (inclusive). If `this` is non-real, then it must be either `start` or `end` if synthetic, otherwise false. Otherwise, we mimic the old style of range checking, which means that non-real starts and ends are treated as less than every real token. * TokenPosition::CompareForSorting(other): Unlike the relational operators, provides a comparison between any types of token positions for purposes such as sorting. Currently only used in the profiler. It also changes TokenPosition::ToCString() to tag synthetic token positions, so they can be distinguished from real ones at a glance. TEST=Existing test suite on trybots, especially the observatory tests which make heavy use of the debugger and the unit tests for the profiler/source report modules. Bug: https://github.com/dart-lang/sdk/issues/44436 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-linux-release-x64-try,vm-kernel-nnbd-linux-release-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try Change-Id: Ic06aa0bc7a1f0fbac7257ed22ca5e7e0ccd7f3f2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174924 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
2020-12-16 08:27:32 +00:00
TokenPosition token_position = TokenPosition::kNoSource;
};
static const 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_ != NULL);
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);
// 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 NULL;
}
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:
explicit Profile(Isolate* isolate);
// Build a filtered model using |filter|.
void Build(Thread* thread, SampleFilter* filter, SampleBuffer* sample_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);
ProfileFunction* FindFunction(const Function& function);
private:
void PrintHeaderJSON(JSONObject* obj);
void ProcessSampleFrameJSON(JSONArray* stack,
ProfileCodeInlinedFunctionsCache* cache,
ProcessedSample* sample,
intptr_t frame_index);
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);
Isolate* isolate_;
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:
static void PrintJSON(JSONStream* stream,
int64_t time_origin_micros,
int64_t time_extent_micros,
bool include_code_samples);
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 PrintNativeAllocationJSON(JSONStream* stream,
int64_t time_origin_micros,
int64_t time_extent_micros,
bool include_code_samples);
static void ClearSamples();
private:
static void PrintJSONImpl(Thread* thread,
JSONStream* stream,
SampleFilter* filter,
SampleBuffer* sample_buffer,
bool include_code_samples);
};
} // namespace dart
#endif // RUNTIME_VM_PROFILER_SERVICE_H_