blob: 11e566c6cf214211a6f5925147252916b39270b7 [file] [log] [blame]
// Copyright (c) 2023, 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_PERFETTO_UTILS_H_
#define RUNTIME_VM_PERFETTO_UTILS_H_
#if defined(SUPPORT_PERFETTO)
#include <memory>
#include <tuple>
#include <utility>
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h"
#include "vm/hash_map.h"
#include "vm/json_stream.h"
#include "vm/os.h"
namespace dart {
namespace perfetto_utils {
inline void SetTrustedPacketSequenceId(
perfetto::protos::pbzero::TracePacket* packet) {
// trusted_packet_sequence_id uniquely identifies a trace producer + writer
// pair. We set the trusted_packet_sequence_id of all packets that we write to
// the arbitrary value of 1.
packet->set_trusted_packet_sequence_id(1);
}
inline void SetTimestampAndMonotonicClockId(
perfetto::protos::pbzero::TracePacket* packet,
int64_t timestamp_micros) {
ASSERT(packet != nullptr);
// TODO(derekx): We should be able to set the unit_multiplier_ns field in a
// ClockSnapshot to avoid manually converting from microseconds to
// nanoseconds, but I haven't been able to get it to work.
packet->set_timestamp(timestamp_micros * 1000);
packet->set_timestamp_clock_id(
perfetto::protos::pbzero::BuiltinClock::BUILTIN_CLOCK_MONOTONIC);
}
inline void PopulateClockSnapshotPacket(
perfetto::protos::pbzero::TracePacket* packet) {
SetTrustedPacketSequenceId(packet);
perfetto::protos::pbzero::ClockSnapshot& clock_snapshot =
*packet->set_clock_snapshot();
clock_snapshot.set_primary_trace_clock(
perfetto::protos::pbzero::BuiltinClock::BUILTIN_CLOCK_MONOTONIC);
perfetto::protos::pbzero::ClockSnapshot_Clock& clock =
*clock_snapshot.add_clocks();
clock.set_clock_id(
perfetto::protos::pbzero::BuiltinClock::BUILTIN_CLOCK_MONOTONIC);
clock.set_timestamp(OS::GetCurrentMonotonicMicrosForTimeline() * 1000);
}
inline void PopulateProcessDescriptorPacket(
perfetto::protos::pbzero::TracePacket* packet) {
perfetto_utils::SetTrustedPacketSequenceId(packet);
perfetto::protos::pbzero::TrackDescriptor& track_descriptor =
*packet->set_track_descriptor();
const int64_t pid = OS::ProcessId();
track_descriptor.set_uuid(pid);
perfetto::protos::pbzero::ProcessDescriptor& process_descriptor =
*track_descriptor.set_process();
process_descriptor.set_pid(pid);
// TODO(derekx): Add the process name.
}
inline const std::tuple<std::unique_ptr<const uint8_t[]>, intptr_t>
GetProtoPreamble(
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet) {
ASSERT(packet != nullptr);
intptr_t size = 0;
for (const protozero::ScatteredHeapBuffer::Slice& slice :
packet->GetSlices()) {
size += slice.size() - slice.unused_bytes();
}
std::unique_ptr<uint8_t[]> preamble =
std::make_unique<uint8_t[]>(perfetto::TracePacket::kMaxPreambleBytes);
uint8_t* ptr = &preamble[0];
const uint8_t tag = protozero::proto_utils::MakeTagLengthDelimited(
perfetto::TracePacket::kPacketFieldNumber);
static_assert(tag < 0x80, "TracePacket tag should fit in one byte");
*(ptr++) = tag;
ptr = protozero::proto_utils::WriteVarInt(size, ptr);
intptr_t preamble_size = reinterpret_cast<intptr_t>(ptr) -
reinterpret_cast<intptr_t>(&preamble[0]);
return std::make_tuple(std::move(preamble), preamble_size);
}
template <typename WriteBytesFunction>
inline void WritePacketBytes(
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet,
WriteBytesFunction&& write_bytes) {
ASSERT(packet != nullptr);
const std::tuple<std::unique_ptr<const uint8_t[]>, intptr_t>& response =
perfetto_utils::GetProtoPreamble(packet);
const uint8_t* preamble = std::get<0>(response).get();
const intptr_t preamble_length = std::get<1>(response);
write_bytes(preamble, preamble_length);
for (const protozero::ScatteredHeapBuffer::Slice& slice :
packet->GetSlices()) {
write_bytes(slice.start(), slice.size() - slice.unused_bytes());
}
}
inline void AppendPacketToJSONBase64String(
JSONBase64String* jsonBase64String,
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet) {
ASSERT(jsonBase64String != nullptr);
WritePacketBytes(packet, [&](auto bytes, auto bytes_length) {
jsonBase64String->AppendBytes(bytes, bytes_length);
});
}
// Sequence of elements which can be interned by |BytesInterner|.
//
// Equality and hash are defined in terms of raw byte content.
template <typename T>
struct InternedBytes {
InternedBytes(const T* data, intptr_t length)
: data(data),
length(length),
hash(HashBytes(reinterpret_cast<const uint8_t*>(data),
length * sizeof(T))),
iid(0) {}
InternedBytes(const T* data, intptr_t length, uword hash, uint64_t iid)
: data(data), length(length), hash(hash), iid(iid) {}
bool Equals(const InternedBytes& other) const {
if (length != other.length) {
return false;
}
return memcmp(data, other.data, length * sizeof(T)) == 0;
}
uword Hash() const { return hash; }
const T* const data;
const intptr_t length;
const uword hash;
// Interning id. Only set after interning and does not participate in
// equality or hash computations.
const uint64_t iid;
};
constexpr uint8_t kInternerWasUsed = 1 << 0;
constexpr uint8_t kInternerHasNewEntries = 1 << 1;
typedef uint8_t InternerStateBits;
// Interning dictionary used to construct various parts of |InternedData|
// message.
template <typename T, typename Allocator>
class BytesInterner
: public BaseDirectChainedHashMap<PointerSetKeyValueTrait<InternedBytes<T>>,
ValueObject,
Allocator> {
using Base =
BaseDirectChainedHashMap<PointerSetKeyValueTrait<InternedBytes<T>>,
ValueObject,
Allocator>;
public:
explicit BytesInterner(Allocator* allocator = nullptr) : Base(allocator) {}
~BytesInterner() {
if constexpr (Allocator::kSupportsFreeingIndividualAllocations) {
auto it = Base::GetIterator();
while (auto pair = it.Next()) {
Dispose(*pair);
}
}
}
uint64_t Intern(const T* data, const intptr_t length) {
state_ |= kInternerWasUsed;
InternedBytes<T> key(data, length);
if (auto interned = Base::Lookup(&key)) {
return (*interned)->iid;
}
state_ |= kInternerHasNewEntries;
const uint64_t iid = Base::Size() + 1;
Base::Insert(Copy(key, iid));
return iid;
}
// Enumerate all entries added to this interner since the last call to this
// function.
template <typename F>
void FlushNewlyInternedTo(F&& callback) {
// Note: we never remove elements from this map so we can just iterate
// |pairs_| linearly.
for (uint32_t i = first_to_flush_; i < Base::next_pair_index_; i++) {
callback(*Base::pairs_[i]);
}
first_to_flush_ = Base::next_pair_index_;
}
InternerStateBits TakeAndResetState() {
const auto result = state_;
state_ = 0;
return result;
}
private:
Allocator* allocator() const { return Base::allocator_; }
InternedBytes<T>* Copy(const InternedBytes<T>& interned, uint64_t iid) const {
auto data_copy = allocator()->template Alloc<T>(interned.length);
memcpy(data_copy, interned.data, interned.length * sizeof(T)); // NOLINT
auto copy = allocator()->template Alloc<InternedBytes<T>>(1);
new (copy) InternedBytes<T>(data_copy, interned.length, interned.hash, iid);
return copy;
}
void Dispose(InternedBytes<T>* interned) {
if constexpr (Allocator::kSupportsFreeingIndividualAllocations) {
allocator()->Free(const_cast<T*>(interned->data),
interned->length * sizeof(T));
allocator()->Free(interned, 1);
}
}
// The index of the first entry which was not flushed via
// |FlushNewlyInternedTo|.
uint32_t first_to_flush_ = 0;
// Combination of |kInternerWasUsed| and |kInternerHasNewEntries|.
InternerStateBits state_ = 0;
};
template <typename Allocator>
class StringInterner : public ValueObject {
public:
explicit StringInterner(Allocator* allocator = nullptr)
: bytes_interner_(allocator) {}
uint64_t Intern(const char* str) {
// +1 to include terminating NUL character.
return bytes_interner_.Intern(str, strlen(str) + 1);
}
InternerStateBits TakeAndResetState() {
return bytes_interner_.TakeAndResetState();
}
template <typename F>
void FlushNewlyInternedTo(F&& callback) {
bytes_interner_.FlushNewlyInternedTo(
[callback = std::move(callback)](const auto& interned_bytes) {
callback(interned_bytes.iid, interned_bytes.data);
});
}
private:
BytesInterner<char, Allocator> bytes_interner_;
};
// Trait used to map 64-bit ids (e.g. isolate or isolate group id) to
// interned id of a corresponding string representation.
//
// This way we only need to generate formatted string once, instead of
// repeatedly formatting it and then interning resulting string to get an
// iid.
class IdToIidTrait {
public:
struct Pair {
uint64_t id;
uint64_t formatted_iid;
};
using Key = uint64_t;
using Value = uint64_t;
static Key KeyOf(const Pair& kv) { return kv.id; }
static Value ValueOf(const Pair& kv) { return kv.formatted_iid; }
static uword Hash(Key key) {
return Utils::WordHash(static_cast<intptr_t>(key));
}
static bool IsKeyEqual(const Pair& kv, Key key) { return kv.id == key; }
};
using IdToIidMap = MallocDirectChainedHashMap<IdToIidTrait>;
class InternedDataBuilder : public ValueObject {
private:
using SequenceFlags = perfetto::protos::pbzero::TracePacket_SequenceFlags;
public:
// InternedData contains multiple independent interning dictionaries which
// are used for different attributes.
#define PERFETTO_INTERNED_STRINGS_FIELDS_LIST(V) \
V(event_categories, name) \
V(event_names, name) \
V(debug_annotation_names, name) \
V(debug_annotation_string_values, str) \
V(function_names, str) \
V(mapping_paths, str)
#define PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(V) \
V(callstacks, uint64_t) \
V(mappings, uint64_t) \
V(frames, uint64_t)
// Direct access for known strings.
#define PERFETTO_COMMON_INTERNED_STRINGS_LIST(V) \
V(debug_annotation_names, isolateId) \
V(debug_annotation_names, isolateGroupId)
InternedDataBuilder() = default;
// Emit all strings added since the last invocation of |AttachInternedDataTo|
// into |interned_data| of the given |TracePacket|.
//
// Mark the packet as depending on incremental state.
void AttachInternedDataTo(perfetto::protos::pbzero::TracePacket* packet) {
const auto interners_state = TakeAndResetStateOfAllInterners();
if ((interners_state & kInternerWasUsed) != 0) {
// At least one interner was used.
packet->set_sequence_flags(sequence_flags_);
}
if ((interners_state & kInternerHasNewEntries) == 0) {
// None of interners have new entries.
return;
}
// The first packet will have SEQ_INCREMENTAL_STATE_CLEARED
// the rest will just have SEQ_NEEDS_INCREMENTAL_STATE.
sequence_flags_ &= ~SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED;
auto interned_data = packet->set_interned_data();
// Flush individual interning dictionaries.
#define FLUSH_FIELD(name, proto_field) \
name##_.FlushNewlyInternedTo([interned_data](auto& iid, auto& str) { \
auto entry = interned_data->add_##name(); \
entry->set_iid(iid); \
entry->set_##proto_field(str); \
});
PERFETTO_INTERNED_STRINGS_FIELDS_LIST(FLUSH_FIELD)
#undef FLUSH_FIELD
callstacks_.FlushNewlyInternedTo([interned_data](const auto& interned) {
auto callstack = interned_data->add_callstacks();
callstack->set_iid(interned.iid);
for (intptr_t i = 0; i < interned.length; i++) {
callstack->add_frame_ids(interned.data[i]);
}
});
mappings_.FlushNewlyInternedTo([interned_data](const auto& interned) {
auto mapping = interned_data->add_mappings();
mapping->set_iid(interned.iid);
mapping->add_path_string_ids(interned.data[0]);
});
frames_.FlushNewlyInternedTo([interned_data](const auto& interned) {
auto frame = interned_data->add_frames();
frame->set_iid(interned.iid);
frame->set_function_name_id(interned.data[0]);
if (interned.data[1] != 0) {
frame->set_mapping_id(interned.data[1]);
}
});
}
#define DEFINE_GETTER(name, ignored) \
perfetto_utils::StringInterner<Malloc>& name() { return name##_; }
PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_GETTER)
#undef DEFINE_GETTER
#define DEFINE_GETTER(name, element_type) \
perfetto_utils::BytesInterner<element_type, Malloc>& name() { \
return name##_; \
}
PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(DEFINE_GETTER)
#undef DEFINE_GETTER
#define DEFINE_GETTER_FOR_COMMON_STRING(category, str) \
uint64_t iid_##str() { \
if (iid_##str##_ == 0) { \
iid_##str##_ = category().Intern(#str); \
} \
return iid_##str##_; \
}
PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_GETTER_FOR_COMMON_STRING)
#undef DEFINE_GETTER_FOR_COMMON_STRING
uint64_t InternFormattedIsolateId(uint64_t isolate_id) {
return InternFormattedIdForDebugAnnotation(
isolate_id_to_iid_of_formatted_string_,
ISOLATE_SERVICE_ID_FORMAT_STRING, isolate_id);
}
uint64_t InternFormattedIsolateGroupId(uint64_t isolate_group_id) {
return InternFormattedIdForDebugAnnotation(
isolate_group_id_to_iid_of_formatted_string_,
ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, isolate_group_id);
}
private:
template <std::size_t kFormatLen>
uint64_t InternFormattedIdForDebugAnnotation(IdToIidMap& cache,
const char (&format)[kFormatLen],
uint64_t id) {
if (auto iid = cache.Lookup(id)) {
return iid->formatted_iid;
}
// 20 characters is enough to format any uint64_t (or int64_t) value.
char formatted[kFormatLen + 20];
Utils::SNPrint(formatted, ARRAY_SIZE(formatted), format, id);
auto formatted_iid = debug_annotation_string_values().Intern(formatted);
cache.Insert({id, formatted_iid});
return formatted_iid;
}
// Returns the union of state of all interners.
InternerStateBits TakeAndResetStateOfAllInterners() {
InternerStateBits result = 0;
#define TAKE_AND_RESET(name, ignored) result |= name##_.TakeAndResetState();
PERFETTO_INTERNED_STRINGS_FIELDS_LIST(TAKE_AND_RESET)
PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(TAKE_AND_RESET)
#undef TAKE_AND_RESET
return result;
}
uint32_t sequence_flags_ = SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED |
SequenceFlags::SEQ_NEEDS_INCREMENTAL_STATE;
// These are interned in debug_annotation_string_values space.
IdToIidMap isolate_id_to_iid_of_formatted_string_;
IdToIidMap isolate_group_id_to_iid_of_formatted_string_;
#define DEFINE_FIELD_FOR_COMMON_STRING(category, str) uint64_t iid_##str##_ = 0;
PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_FIELD_FOR_COMMON_STRING)
#undef DEFINE_FIELD_FOR_COMMON_STRING
#define DEFINE_FIELD(name, proto_field) \
perfetto_utils::StringInterner<Malloc> name##_;
PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_FIELD)
#undef DEFINE_FIELD
#define DEFINE_FIELD(name, element_type) \
perfetto_utils::BytesInterner<element_type, Malloc> name##_;
PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(DEFINE_FIELD)
#undef DEFINE_FIELD
DISALLOW_COPY_AND_ASSIGN(InternedDataBuilder);
};
} // namespace perfetto_utils
} // namespace dart
#endif // defined(SUPPORT_PERFETTO)
#endif // RUNTIME_VM_PERFETTO_UTILS_H_