[vm] Speedup profile data streaming in AOT
Current implementation of profile data streaming inherited its
approach to symbolization from the implementation of vm-service's
get{,Perfetto}CpuSamples methods. These methods rather expensive
as they rely on CodeLookupTable to symbolize collected samples, and
constructing CodeLookupTable requires bringing all threads to safepoint
and iterating over old-space to collect code objects. This can take
significant amount of time - especially when old-space is large (e.g.
consider 1Gb+ heaps of Dart Analysis Server).
This CL rewrites profile data streaming to use a completely different
approach in AOT mode where Dart frames are not symbolized eagerly
and instead stored in the timeline in their raw form: a pair of
an isolate group specific Mapping and a PC value relative to the start
of that mapping. At the end of streaming (or when isolate group
exits) an additional ModuleSymbols packet is emitted which provides
symbolization information for all collected frames. ModuleSymbols
mappings can be cheaply constructed from collected PCs using
ReversePc lookup tables.
TEST=expanded existing tests
Change-Id: I56ef1dd4c9a17fb0d2e9c24e51f2e4656a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482782
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
a2b1bb421a
commit
d7fa1ced6a
+19
@@ -63,6 +63,11 @@ class InternedData_Decoder
|
||||
debug_annotation_names() const {
|
||||
return GetRepeated<::protozero::ConstBytes>(3);
|
||||
}
|
||||
bool has_build_ids() const { return at<16>().valid(); }
|
||||
::protozero::RepeatedFieldIterator<::protozero::ConstBytes> build_ids()
|
||||
const {
|
||||
return GetRepeated<::protozero::ConstBytes>(16);
|
||||
}
|
||||
bool has_mapping_paths() const { return at<17>().valid(); }
|
||||
::protozero::RepeatedFieldIterator<::protozero::ConstBytes> mapping_paths()
|
||||
const {
|
||||
@@ -100,6 +105,7 @@ class InternedData : public ::protozero::Message {
|
||||
kEventCategoriesFieldNumber = 1,
|
||||
kEventNamesFieldNumber = 2,
|
||||
kDebugAnnotationNamesFieldNumber = 3,
|
||||
kBuildIdsFieldNumber = 16,
|
||||
kMappingPathsFieldNumber = 17,
|
||||
kFunctionNamesFieldNumber = 5,
|
||||
kMappingsFieldNumber = 19,
|
||||
@@ -151,6 +157,19 @@ class InternedData : public ::protozero::Message {
|
||||
return BeginNestedMessage<T>(3);
|
||||
}
|
||||
|
||||
using FieldMetadata_BuildIds = ::protozero::proto_utils::FieldMetadata<
|
||||
16,
|
||||
::protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
|
||||
::protozero::proto_utils::ProtoSchemaType::kMessage,
|
||||
InternedString,
|
||||
InternedData>;
|
||||
|
||||
static constexpr FieldMetadata_BuildIds kBuildIds{};
|
||||
template <typename T = InternedString>
|
||||
T* add_build_ids() {
|
||||
return BeginNestedMessage<T>(16);
|
||||
}
|
||||
|
||||
using FieldMetadata_MappingPaths = ::protozero::proto_utils::FieldMetadata<
|
||||
17,
|
||||
::protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
|
||||
|
||||
@@ -65,6 +65,8 @@ message InternedData {
|
||||
|
||||
// Note: field IDs up to 15 should be used for frequent data only.
|
||||
|
||||
// Build IDs of exectuable files.
|
||||
repeated InternedString build_ids = 16;
|
||||
// Paths to executable files.
|
||||
repeated InternedString mapping_paths = 17;
|
||||
// Names of functions used in frames below.
|
||||
|
||||
+282
@@ -20,6 +20,15 @@
|
||||
#include "perfetto/protozero/proto_decoder.h"
|
||||
#include "perfetto/protozero/proto_utils.h"
|
||||
|
||||
namespace perfetto {
|
||||
namespace protos {
|
||||
namespace pbzero {
|
||||
class AddressSymbols;
|
||||
class Line;
|
||||
} // Namespace pbzero.
|
||||
} // Namespace protos.
|
||||
} // Namespace perfetto.
|
||||
|
||||
namespace perfetto {
|
||||
namespace protos {
|
||||
namespace pbzero {
|
||||
@@ -206,6 +215,8 @@ class Mapping_Decoder
|
||||
: TypedProtoDecoder(raw.data, raw.size) {}
|
||||
bool has_iid() const { return at<1>().valid(); }
|
||||
uint64_t iid() const { return at<1>().as_uint64(); }
|
||||
bool has_build_id() const { return at<2>().valid(); }
|
||||
uint64_t build_id() const { return at<2>().as_uint64(); }
|
||||
bool has_start_offset() const { return at<3>().valid(); }
|
||||
uint64_t start_offset() const { return at<3>().as_uint64(); }
|
||||
bool has_start() const { return at<4>().valid(); }
|
||||
@@ -223,6 +234,7 @@ class Mapping : public ::protozero::Message {
|
||||
using Decoder = Mapping_Decoder;
|
||||
enum : int32_t {
|
||||
kIidFieldNumber = 1,
|
||||
kBuildIdFieldNumber = 2,
|
||||
kStartOffsetFieldNumber = 3,
|
||||
kStartFieldNumber = 4,
|
||||
kEndFieldNumber = 5,
|
||||
@@ -248,6 +260,24 @@ class Mapping : public ::protozero::Message {
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_BuildId = ::protozero::proto_utils::FieldMetadata<
|
||||
2,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint64,
|
||||
uint64_t,
|
||||
Mapping>;
|
||||
|
||||
static constexpr FieldMetadata_BuildId kBuildId{};
|
||||
void set_build_id(uint64_t value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_BuildId::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint64>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_StartOffset = ::protozero::proto_utils::FieldMetadata<
|
||||
3,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
@@ -321,6 +351,258 @@ class Mapping : public ::protozero::Message {
|
||||
}
|
||||
};
|
||||
|
||||
class ModuleSymbols_Decoder
|
||||
: public ::protozero::TypedProtoDecoder</*MAX_FIELD_ID=*/3> {
|
||||
public:
|
||||
ModuleSymbols_Decoder(const uint8_t* data, size_t len)
|
||||
: TypedProtoDecoder(data, len) {}
|
||||
explicit ModuleSymbols_Decoder(const std::string& raw)
|
||||
: TypedProtoDecoder(reinterpret_cast<const uint8_t*>(raw.data()),
|
||||
raw.size()) {}
|
||||
explicit ModuleSymbols_Decoder(const ::protozero::ConstBytes& raw)
|
||||
: TypedProtoDecoder(raw.data, raw.size) {}
|
||||
bool has_path() const { return at<1>().valid(); }
|
||||
::protozero::ConstChars path() const { return at<1>().as_string(); }
|
||||
bool has_build_id() const { return at<2>().valid(); }
|
||||
::protozero::ConstChars build_id() const { return at<2>().as_string(); }
|
||||
bool has_address_symbols() const { return at<3>().valid(); }
|
||||
::protozero::RepeatedFieldIterator<::protozero::ConstBytes> address_symbols()
|
||||
const {
|
||||
return GetRepeated<::protozero::ConstBytes>(3);
|
||||
}
|
||||
};
|
||||
|
||||
class ModuleSymbols : public ::protozero::Message {
|
||||
public:
|
||||
using Decoder = ModuleSymbols_Decoder;
|
||||
enum : int32_t {
|
||||
kPathFieldNumber = 1,
|
||||
kBuildIdFieldNumber = 2,
|
||||
kAddressSymbolsFieldNumber = 3,
|
||||
};
|
||||
static constexpr const char* GetName() {
|
||||
return ".perfetto.protos.ModuleSymbols";
|
||||
}
|
||||
|
||||
using FieldMetadata_Path = ::protozero::proto_utils::FieldMetadata<
|
||||
1,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kString,
|
||||
std::string,
|
||||
ModuleSymbols>;
|
||||
|
||||
static constexpr FieldMetadata_Path kPath{};
|
||||
void set_path(const char* data, size_t size) {
|
||||
AppendBytes(FieldMetadata_Path::kFieldId, data, size);
|
||||
}
|
||||
void set_path(::protozero::ConstChars chars) {
|
||||
AppendBytes(FieldMetadata_Path::kFieldId, chars.data, chars.size);
|
||||
}
|
||||
void set_path(std::string value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_Path::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kString>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_BuildId = ::protozero::proto_utils::FieldMetadata<
|
||||
2,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kString,
|
||||
std::string,
|
||||
ModuleSymbols>;
|
||||
|
||||
static constexpr FieldMetadata_BuildId kBuildId{};
|
||||
void set_build_id(const char* data, size_t size) {
|
||||
AppendBytes(FieldMetadata_BuildId::kFieldId, data, size);
|
||||
}
|
||||
void set_build_id(::protozero::ConstChars chars) {
|
||||
AppendBytes(FieldMetadata_BuildId::kFieldId, chars.data, chars.size);
|
||||
}
|
||||
void set_build_id(std::string value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_BuildId::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kString>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_AddressSymbols = ::protozero::proto_utils::FieldMetadata<
|
||||
3,
|
||||
::protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
|
||||
::protozero::proto_utils::ProtoSchemaType::kMessage,
|
||||
AddressSymbols,
|
||||
ModuleSymbols>;
|
||||
|
||||
static constexpr FieldMetadata_AddressSymbols kAddressSymbols{};
|
||||
template <typename T = AddressSymbols>
|
||||
T* add_address_symbols() {
|
||||
return BeginNestedMessage<T>(3);
|
||||
}
|
||||
};
|
||||
|
||||
class AddressSymbols_Decoder
|
||||
: public ::protozero::TypedProtoDecoder</*MAX_FIELD_ID=*/2> {
|
||||
public:
|
||||
AddressSymbols_Decoder(const uint8_t* data, size_t len)
|
||||
: TypedProtoDecoder(data, len) {}
|
||||
explicit AddressSymbols_Decoder(const std::string& raw)
|
||||
: TypedProtoDecoder(reinterpret_cast<const uint8_t*>(raw.data()),
|
||||
raw.size()) {}
|
||||
explicit AddressSymbols_Decoder(const ::protozero::ConstBytes& raw)
|
||||
: TypedProtoDecoder(raw.data, raw.size) {}
|
||||
bool has_address() const { return at<1>().valid(); }
|
||||
uint64_t address() const { return at<1>().as_uint64(); }
|
||||
bool has_lines() const { return at<2>().valid(); }
|
||||
::protozero::RepeatedFieldIterator<::protozero::ConstBytes> lines() const {
|
||||
return GetRepeated<::protozero::ConstBytes>(2);
|
||||
}
|
||||
};
|
||||
|
||||
class AddressSymbols : public ::protozero::Message {
|
||||
public:
|
||||
using Decoder = AddressSymbols_Decoder;
|
||||
enum : int32_t {
|
||||
kAddressFieldNumber = 1,
|
||||
kLinesFieldNumber = 2,
|
||||
};
|
||||
static constexpr const char* GetName() {
|
||||
return ".perfetto.protos.AddressSymbols";
|
||||
}
|
||||
|
||||
using FieldMetadata_Address = ::protozero::proto_utils::FieldMetadata<
|
||||
1,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint64,
|
||||
uint64_t,
|
||||
AddressSymbols>;
|
||||
|
||||
static constexpr FieldMetadata_Address kAddress{};
|
||||
void set_address(uint64_t value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_Address::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint64>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_Lines = ::protozero::proto_utils::FieldMetadata<
|
||||
2,
|
||||
::protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
|
||||
::protozero::proto_utils::ProtoSchemaType::kMessage,
|
||||
Line,
|
||||
AddressSymbols>;
|
||||
|
||||
static constexpr FieldMetadata_Lines kLines{};
|
||||
template <typename T = Line>
|
||||
T* add_lines() {
|
||||
return BeginNestedMessage<T>(2);
|
||||
}
|
||||
};
|
||||
|
||||
class Line_Decoder : public ::protozero::TypedProtoDecoder</*MAX_FIELD_ID=*/3> {
|
||||
public:
|
||||
Line_Decoder(const uint8_t* data, size_t len)
|
||||
: TypedProtoDecoder(data, len) {}
|
||||
explicit Line_Decoder(const std::string& raw)
|
||||
: TypedProtoDecoder(reinterpret_cast<const uint8_t*>(raw.data()),
|
||||
raw.size()) {}
|
||||
explicit Line_Decoder(const ::protozero::ConstBytes& raw)
|
||||
: TypedProtoDecoder(raw.data, raw.size) {}
|
||||
bool has_function_name() const { return at<1>().valid(); }
|
||||
::protozero::ConstChars function_name() const { return at<1>().as_string(); }
|
||||
bool has_source_file_name() const { return at<2>().valid(); }
|
||||
::protozero::ConstChars source_file_name() const {
|
||||
return at<2>().as_string();
|
||||
}
|
||||
bool has_line_number() const { return at<3>().valid(); }
|
||||
uint32_t line_number() const { return at<3>().as_uint32(); }
|
||||
};
|
||||
|
||||
class Line : public ::protozero::Message {
|
||||
public:
|
||||
using Decoder = Line_Decoder;
|
||||
enum : int32_t {
|
||||
kFunctionNameFieldNumber = 1,
|
||||
kSourceFileNameFieldNumber = 2,
|
||||
kLineNumberFieldNumber = 3,
|
||||
};
|
||||
static constexpr const char* GetName() { return ".perfetto.protos.Line"; }
|
||||
|
||||
using FieldMetadata_FunctionName = ::protozero::proto_utils::FieldMetadata<
|
||||
1,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kString,
|
||||
std::string,
|
||||
Line>;
|
||||
|
||||
static constexpr FieldMetadata_FunctionName kFunctionName{};
|
||||
void set_function_name(const char* data, size_t size) {
|
||||
AppendBytes(FieldMetadata_FunctionName::kFieldId, data, size);
|
||||
}
|
||||
void set_function_name(::protozero::ConstChars chars) {
|
||||
AppendBytes(FieldMetadata_FunctionName::kFieldId, chars.data, chars.size);
|
||||
}
|
||||
void set_function_name(std::string value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_FunctionName::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kString>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_SourceFileName = ::protozero::proto_utils::FieldMetadata<
|
||||
2,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kString,
|
||||
std::string,
|
||||
Line>;
|
||||
|
||||
static constexpr FieldMetadata_SourceFileName kSourceFileName{};
|
||||
void set_source_file_name(const char* data, size_t size) {
|
||||
AppendBytes(FieldMetadata_SourceFileName::kFieldId, data, size);
|
||||
}
|
||||
void set_source_file_name(::protozero::ConstChars chars) {
|
||||
AppendBytes(FieldMetadata_SourceFileName::kFieldId, chars.data, chars.size);
|
||||
}
|
||||
void set_source_file_name(std::string value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_SourceFileName::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kString>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
|
||||
using FieldMetadata_LineNumber = ::protozero::proto_utils::FieldMetadata<
|
||||
3,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint32,
|
||||
uint32_t,
|
||||
Line>;
|
||||
|
||||
static constexpr FieldMetadata_LineNumber kLineNumber{};
|
||||
void set_line_number(uint32_t value) {
|
||||
static constexpr uint32_t field_id = FieldMetadata_LineNumber::kFieldId;
|
||||
// Call the appropriate protozero::Message::Append(field_id, ...)
|
||||
// method based on the type of the field.
|
||||
::protozero::internal::FieldWriter<
|
||||
::protozero::proto_utils::ProtoSchemaType::kUint32>::Append(*this,
|
||||
field_id,
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
class InternedString_Decoder
|
||||
: public ::protozero::TypedProtoDecoder</*MAX_FIELD_ID=*/2> {
|
||||
public:
|
||||
|
||||
@@ -37,10 +37,49 @@ message InternedString {
|
||||
optional bytes str = 2;
|
||||
}
|
||||
|
||||
// Source line info.
|
||||
message Line {
|
||||
optional string function_name = 1;
|
||||
optional string source_file_name = 2;
|
||||
optional uint32 line_number = 3;
|
||||
}
|
||||
|
||||
// Symbols for a given address in a module.
|
||||
message AddressSymbols {
|
||||
optional uint64 address = 1;
|
||||
|
||||
// Source lines that correspond to this address.
|
||||
//
|
||||
// These are repeated because when inlining happens, multiple functions'
|
||||
// frames can be at a single address. Imagine function Foo calling the
|
||||
// std::vector<int> constructor, which gets inlined at 0xf00. We then get
|
||||
// both Foo and the std::vector<int> constructor when we symbolize the
|
||||
// address.
|
||||
repeated Line lines = 2;
|
||||
}
|
||||
|
||||
// Symbols for addresses seen in a module.
|
||||
// Used in re-symbolisation of complete traces.
|
||||
message ModuleSymbols {
|
||||
// Fully qualified path to the mapping.
|
||||
// E.g. /system/lib64/libc.so.
|
||||
optional string path = 1;
|
||||
|
||||
// .note.gnu.build-id on Linux (not hex encoded).
|
||||
// uuid on MacOS.
|
||||
// Module GUID on Windows.
|
||||
optional string build_id = 2;
|
||||
repeated AddressSymbols address_symbols = 3;
|
||||
}
|
||||
|
||||
message Mapping {
|
||||
// Interning key.
|
||||
optional uint64 iid = 1;
|
||||
|
||||
// Interning key.
|
||||
// Starts from 1, 0 is the same as "not set".
|
||||
optional uint64 build_id = 2;
|
||||
|
||||
// The linker may create multiple memory mappings for the same shared
|
||||
// library.
|
||||
// This is so that the ELF header is mapped as read only, while the
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace protos {
|
||||
namespace pbzero {
|
||||
class ClockSnapshot;
|
||||
class InternedData;
|
||||
class ModuleSymbols;
|
||||
class PerfSample;
|
||||
class TrackDescriptor;
|
||||
class TrackEvent;
|
||||
@@ -91,6 +92,8 @@ class TracePacket_Decoder
|
||||
::protozero::ConstBytes track_descriptor() const {
|
||||
return at<60>().as_bytes();
|
||||
}
|
||||
bool has_module_symbols() const { return at<61>().valid(); }
|
||||
::protozero::ConstBytes module_symbols() const { return at<61>().as_bytes(); }
|
||||
bool has_perf_sample() const { return at<66>().valid(); }
|
||||
::protozero::ConstBytes perf_sample() const { return at<66>().as_bytes(); }
|
||||
bool has_trusted_packet_sequence_id() const { return at<10>().valid(); }
|
||||
@@ -110,6 +113,7 @@ class TracePacket : public ::protozero::Message {
|
||||
kClockSnapshotFieldNumber = 6,
|
||||
kTrackEventFieldNumber = 11,
|
||||
kTrackDescriptorFieldNumber = 60,
|
||||
kModuleSymbolsFieldNumber = 61,
|
||||
kPerfSampleFieldNumber = 66,
|
||||
kTrustedPacketSequenceIdFieldNumber = 10,
|
||||
kInternedDataFieldNumber = 12,
|
||||
@@ -207,6 +211,19 @@ class TracePacket : public ::protozero::Message {
|
||||
return BeginNestedMessage<T>(60);
|
||||
}
|
||||
|
||||
using FieldMetadata_ModuleSymbols = ::protozero::proto_utils::FieldMetadata<
|
||||
61,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
::protozero::proto_utils::ProtoSchemaType::kMessage,
|
||||
ModuleSymbols,
|
||||
TracePacket>;
|
||||
|
||||
static constexpr FieldMetadata_ModuleSymbols kModuleSymbols{};
|
||||
template <typename T = ModuleSymbols>
|
||||
T* set_module_symbols() {
|
||||
return BeginNestedMessage<T>(61);
|
||||
}
|
||||
|
||||
using FieldMetadata_PerfSample = ::protozero::proto_utils::FieldMetadata<
|
||||
66,
|
||||
::protozero::proto_utils::RepetitionType::kNotRepeated,
|
||||
|
||||
@@ -26,6 +26,7 @@ syntax = "proto2";
|
||||
|
||||
import "protos/perfetto/trace/clock_snapshot.proto";
|
||||
import "protos/perfetto/trace/interned_data/interned_data.proto";
|
||||
import "protos/perfetto/trace/profiling/profile_common.proto";
|
||||
import "protos/perfetto/trace/profiling/profile_packet.proto";
|
||||
import "protos/perfetto/trace/track_event/track_descriptor.proto";
|
||||
import "protos/perfetto/trace/track_event/track_event.proto";
|
||||
@@ -79,6 +80,9 @@ message TracePacket {
|
||||
// Only used by TrackEvent.
|
||||
TrackDescriptor track_descriptor = 60;
|
||||
|
||||
// Only used in profile packets.
|
||||
ModuleSymbols module_symbols = 61;
|
||||
|
||||
PerfSample perf_sample = 66;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user