You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
13 KiB
348 lines
13 KiB
#include "perfetto.h"
|
|
#include "perfetto-tracing-only.h"
|
|
#include "perfetto_trace.pb.h"
|
|
|
|
#include <string>
|
|
#include <thread>
|
|
#include <fstream>
|
|
|
|
PERFETTO_DEFINE_CATEGORIES(
|
|
::perfetto::Category("gfx")
|
|
.SetDescription("Events from the graphics subsystem"));
|
|
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
|
|
|
|
#define TRACE_COUNTER(category, name, value) \
|
|
PERFETTO_INTERNAL_TRACK_EVENT( \
|
|
category, name, \
|
|
::perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER, [&](::perfetto::EventContext ctx){ \
|
|
ctx.event()->set_counter_value(value); \
|
|
})
|
|
|
|
#ifdef __cplusplus
|
|
# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
|
|
# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
|
|
#else
|
|
# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
|
|
# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
|
|
#endif
|
|
|
|
namespace virtualdeviceperfetto {
|
|
|
|
static bool sPerfettoInitialized = false;
|
|
|
|
static VirtualDeviceTraceConfig sTraceConfig = {
|
|
.initialized = false,
|
|
.tracingDisabled = true,
|
|
.packetsWritten = 0,
|
|
.sequenceIdWritten = 0,
|
|
.currentInterningId = 1,
|
|
.currentThreadId = 1,
|
|
.hostFilename = "vmm.trace",
|
|
.guestFilename = nullptr,
|
|
.combinedFilename = nullptr,
|
|
.hostStartTime = 0,
|
|
.guestStartTime = 0,
|
|
.guestTimeDiff = 0,
|
|
.perThreadStorageMb = 1,
|
|
};
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void setTraceConfig(std::function<void(VirtualDeviceTraceConfig&)> f) {
|
|
f(sTraceConfig);
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT VirtualDeviceTraceConfig queryTraceConfig() {
|
|
return sTraceConfig;
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void initialize(const bool** tracingDisabledPtr) {
|
|
if (!sPerfettoInitialized) {
|
|
::perfetto::TracingInitArgs args;
|
|
args.backends |= ::perfetto::kInProcessBackend;
|
|
::perfetto::Tracing::Initialize(args);
|
|
::perfetto::TrackEvent::Register();
|
|
sPerfettoInitialized = true;
|
|
}
|
|
|
|
// An optimization to have faster queries of whether tracing is enabled.
|
|
*tracingDisabledPtr = &sTraceConfig.tracingDisabled;
|
|
}
|
|
|
|
static std::unique_ptr<::perfetto::TracingSession> sTracingSession;
|
|
|
|
bool useFilenameByEnv(const char* s) {
|
|
return s && ("" != std::string(s));
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void enableTracing() {
|
|
const char* hostFilenameByEnv = std::getenv("VPERFETTO_HOST_FILE");
|
|
const char* guestFilenameByEnv = std::getenv("VPERFETTO_GUEST_FILE");
|
|
const char* combinedFilenameByEnv = std::getenv("VPERFETTO_COMBINED_FILE");
|
|
|
|
if (useFilenameByEnv(hostFilenameByEnv)) {
|
|
sTraceConfig.hostFilename = hostFilenameByEnv;
|
|
}
|
|
|
|
if (useFilenameByEnv(guestFilenameByEnv)) {
|
|
sTraceConfig.guestFilename = guestFilenameByEnv;
|
|
}
|
|
|
|
if (useFilenameByEnv(combinedFilenameByEnv)) {
|
|
sTraceConfig.combinedFilename = combinedFilenameByEnv;
|
|
}
|
|
|
|
// Don't enable tracing if host filename is null
|
|
if (!sTraceConfig.hostFilename) return;
|
|
|
|
// Don't enable it twice
|
|
if (!sTraceConfig.tracingDisabled) return;
|
|
|
|
if (!sTracingSession) {
|
|
fprintf(stderr, "%s: Tracing begins================================================================================\n", __func__);
|
|
fprintf(stderr, "%s: Configuration:\n", __func__);
|
|
fprintf(stderr, "%s: host filename: %s (possibly set via $VPERFETTO_HOST_FILE)\n", __func__, sTraceConfig.hostFilename);
|
|
fprintf(stderr, "%s: guest filename: %s (possibly set via $VPERFETTO_GUEST_FILE)\n", __func__, sTraceConfig.guestFilename);
|
|
fprintf(stderr, "%s: combined filename: %s (possibly set via $VPERFETTO_COMBINED_FILE)\n", __func__, sTraceConfig.combinedFilename);
|
|
fprintf(stderr, "%s: guest time diff to add to host time: %llu\n", __func__, (unsigned long long)sTraceConfig.guestTimeDiff);
|
|
|
|
auto desc = ::perfetto::ProcessTrack::Current().Serialize();
|
|
desc.mutable_process()->set_process_name("VirtualMachineMonitorProcess");
|
|
::perfetto::TrackEvent::SetTrackDescriptor(::perfetto::ProcessTrack::Current(), desc);
|
|
|
|
::perfetto::TraceConfig cfg;
|
|
::perfetto::protos::gen::TrackEventConfig track_event_cfg;
|
|
cfg.add_buffers()->set_size_kb(1024 * 100); // Record up to 100 MiB.
|
|
auto* ds_cfg = cfg.add_data_sources()->mutable_config();
|
|
ds_cfg->set_name("track_event");
|
|
ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString());
|
|
|
|
// Disable service events in the host trace, because they interfere
|
|
// with the guest's and we end up dropping packets on one side or the other.
|
|
auto* builtin_ds_cfg = cfg.mutable_builtin_data_sources();
|
|
builtin_ds_cfg->set_disable_service_events(true);
|
|
|
|
sTracingSession = ::perfetto::Tracing::NewTrace();
|
|
sTracingSession->Setup(cfg);
|
|
sTracingSession->StartBlocking();
|
|
sTraceConfig.tracingDisabled = false;
|
|
}
|
|
}
|
|
|
|
void asyncTraceSaveFunc() {
|
|
fprintf(stderr, "%s: Saving combined trace async...\n", __func__);
|
|
|
|
static const int kWaitSecondsPerIteration = 1;
|
|
static const int kMaxIters = 20;
|
|
static const int kMinItersForGuestFileSize = 2;
|
|
|
|
const char* hostFilename = sTraceConfig.hostFilename;
|
|
const char* guestFilename = sTraceConfig.guestFilename;
|
|
const char* combinedFilename = sTraceConfig.combinedFilename;
|
|
|
|
std::streampos currGuestSize = 0;
|
|
int numGoodGuestFileSizeIters = 0;
|
|
bool good = false;
|
|
|
|
for (int i = 0; i < kMaxIters; ++i) {
|
|
fprintf(stderr, "%s: Waiting for 1 second...\n", __func__);
|
|
std::this_thread::sleep_for(std::chrono::seconds(kWaitSecondsPerIteration));
|
|
fprintf(stderr, "%s: Querying file size of guest trace...\n", __func__);
|
|
std::ifstream guestFile(guestFilename, std::ios::in | std::ios::binary | std::ios::ate);
|
|
std::streampos size = guestFile.tellg();
|
|
|
|
if (!size) {
|
|
fprintf(stderr, "%s: No size, try again\n", __func__);
|
|
continue;
|
|
}
|
|
|
|
if (size != currGuestSize) {
|
|
fprintf(stderr, "%s: Sized changed (%llu to %llu), try again\n", __func__,
|
|
(unsigned long long)currGuestSize, (unsigned long long)size);
|
|
currGuestSize = size;
|
|
continue;
|
|
}
|
|
|
|
++numGoodGuestFileSizeIters;
|
|
|
|
if (numGoodGuestFileSizeIters == kMinItersForGuestFileSize) {
|
|
fprintf(stderr, "%s: size is stable, continue saving\n", __func__);
|
|
good = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!good) {
|
|
fprintf(stderr, "%s: Timed out when waiting for guest file to stabilize, skipping combined trace saving.\n", __func__);
|
|
return;
|
|
}
|
|
|
|
std::ifstream hostFile(hostFilename, std::ios_base::binary);
|
|
std::ifstream guestFile(guestFilename, std::ios_base::binary);
|
|
std::ofstream combinedFile(combinedFilename, std::ios::out | std::ios_base::binary);
|
|
|
|
combinedFile << guestFile.rdbuf() << hostFile.rdbuf();
|
|
|
|
fprintf(stderr, "%s: Wrote combined trace (%s)\n", __func__, combinedFilename);
|
|
}
|
|
|
|
template <typename T>
|
|
static inline size_t varIntEncodingSize(T value) {
|
|
// If value is <= 0 we must first sign extend to int64_t (see [1]).
|
|
// Finally we always cast to an unsigned value to to avoid arithmetic
|
|
// (sign expanding) shifts in the while loop.
|
|
// [1]: "If you use int32 or int64 as the type for a negative number, the
|
|
// resulting varint is always ten bytes long".
|
|
// - developers.google.com/protocol-buffers/docs/encoding
|
|
// So for each input type we do the following casts:
|
|
// uintX_t -> uintX_t -> uintX_t
|
|
// int8_t -> int64_t -> uint64_t
|
|
// int16_t -> int64_t -> uint64_t
|
|
// int32_t -> int64_t -> uint64_t
|
|
// int64_t -> int64_t -> uint64_t
|
|
using MaybeExtendedType =
|
|
typename std::conditional<std::is_unsigned<T>::value, T, int64_t>::type;
|
|
using UnsignedType = typename std::make_unsigned<MaybeExtendedType>::type;
|
|
|
|
MaybeExtendedType extended_value = static_cast<MaybeExtendedType>(value);
|
|
UnsignedType unsigned_value = static_cast<UnsignedType>(extended_value);
|
|
|
|
size_t bytes = 0;
|
|
|
|
while (unsigned_value >= 0x80) {
|
|
++bytes;
|
|
unsigned_value >>= 7;
|
|
}
|
|
|
|
return bytes + 1;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline uint8_t* writeVarInt(T value, uint8_t* target) {
|
|
// If value is <= 0 we must first sign extend to int64_t (see [1]).
|
|
// Finally we always cast to an unsigned value to to avoid arithmetic
|
|
// (sign expanding) shifts in the while loop.
|
|
// [1]: "If you use int32 or int64 as the type for a negative number, the
|
|
// resulting varint is always ten bytes long".
|
|
// - developers.google.com/protocol-buffers/docs/encoding
|
|
// So for each input type we do the following casts:
|
|
// uintX_t -> uintX_t -> uintX_t
|
|
// int8_t -> int64_t -> uint64_t
|
|
// int16_t -> int64_t -> uint64_t
|
|
// int32_t -> int64_t -> uint64_t
|
|
// int64_t -> int64_t -> uint64_t
|
|
using MaybeExtendedType =
|
|
typename std::conditional<std::is_unsigned<T>::value, T, int64_t>::type;
|
|
using UnsignedType = typename std::make_unsigned<MaybeExtendedType>::type;
|
|
|
|
MaybeExtendedType extended_value = static_cast<MaybeExtendedType>(value);
|
|
UnsignedType unsigned_value = static_cast<UnsignedType>(extended_value);
|
|
|
|
while (unsigned_value >= 0x80) {
|
|
*target++ = static_cast<uint8_t>(unsigned_value) | 0x80;
|
|
unsigned_value >>= 7;
|
|
}
|
|
*target = static_cast<uint8_t>(unsigned_value);
|
|
return target + 1;
|
|
}
|
|
|
|
static std::vector<char> sProcessTrace(const std::vector<char>& trace) {
|
|
std::vector<char> res;
|
|
|
|
::perfetto::protos::Trace pbtrace;
|
|
std::string traceStr(trace.begin(), trace.end());
|
|
|
|
if (pbtrace.ParseFromString(traceStr)) {
|
|
} else {
|
|
fprintf(stderr, "%s: Failed to parse protobuf as a string\n", __func__);
|
|
return res;
|
|
}
|
|
|
|
fprintf(stderr, "%s: postprocessing trace with guest time diff of %llu\n", __func__,
|
|
(unsigned long long)sTraceConfig.guestTimeDiff);
|
|
|
|
for (int i = 0; i < pbtrace.packet_size(); ++i) {
|
|
// Process one trace packet.
|
|
auto* packet = pbtrace.mutable_packet(i);
|
|
if (packet->has_timestamp()) {
|
|
packet->set_timestamp(packet->timestamp() + sTraceConfig.guestTimeDiff);
|
|
}
|
|
}
|
|
|
|
std::string traceAfter;
|
|
pbtrace.SerializeToString(&traceAfter);
|
|
|
|
std::vector<char> res2(traceAfter.begin(), traceAfter.end());
|
|
return res2;
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void disableTracing() {
|
|
if (sTracingSession) {
|
|
sTraceConfig.tracingDisabled = true;
|
|
sTracingSession->StopBlocking();
|
|
std::vector<char> trace_data(sTracingSession->ReadTraceBlocking());
|
|
|
|
std::vector<char> processed =
|
|
sProcessTrace(trace_data);
|
|
|
|
fprintf(stderr, "%s: Tracing ended================================================================================\n", __func__);
|
|
fprintf(stderr, "%s: Saving trace to disk. Configuration:\n", __func__);
|
|
fprintf(stderr, "%s: host filename: %s\n", __func__, sTraceConfig.hostFilename);
|
|
fprintf(stderr, "%s: guest filename: %s\n", __func__, sTraceConfig.guestFilename);
|
|
fprintf(stderr, "%s: combined filename: %s\n", __func__, sTraceConfig.combinedFilename);
|
|
|
|
fprintf(stderr, "%s: Saving host trace first...\n", __func__);
|
|
|
|
// Write the trace into a file.
|
|
std::ofstream output;
|
|
output.open(sTraceConfig.hostFilename, std::ios::out | std::ios::binary);
|
|
output.write(&processed[0], processed.size());
|
|
output.close();
|
|
sTracingSession.reset();
|
|
|
|
fprintf(stderr, "%s: Saving host trace first...(done)\n", __func__);
|
|
|
|
if (!sTraceConfig.guestFilename || !sTraceConfig.combinedFilename) {
|
|
fprintf(stderr, "%s: skipping guest combined trace, "
|
|
"either guest file name (%p) not specified or "
|
|
"combined file name (%p) not specified\n", __func__,
|
|
sTraceConfig.guestFilename,
|
|
sTraceConfig.combinedFilename);
|
|
return;
|
|
}
|
|
|
|
std::thread saveThread(asyncTraceSaveFunc);
|
|
saveThread.detach();
|
|
}
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void beginTrace(const char* eventName) {
|
|
TRACE_EVENT_BEGIN("gfx", ::perfetto::StaticString{eventName});
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void endTrace() {
|
|
TRACE_EVENT_END("gfx");
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void traceCounter(const char* name, int64_t value) {
|
|
// TODO: What this really needs until its supported in the official sdk:
|
|
// a. a static global to track uuids and names for counters
|
|
// b. track objects generated dynamically
|
|
// c. setting the descriptor of these track objects
|
|
// if (CC_LIKELY(sTraceConfig.tracingDisabled)) return;
|
|
// TRACE_COUNTER("gfx", ::perfetto::StaticString{name}, value);
|
|
}
|
|
|
|
PERFETTO_TRACING_ONLY_EXPORT void setGuestTime(uint64_t t) {
|
|
virtualdeviceperfetto::setTraceConfig([t](virtualdeviceperfetto::VirtualDeviceTraceConfig& config) {
|
|
// can only be set before tracing
|
|
if (!config.tracingDisabled) {
|
|
return;
|
|
}
|
|
config.guestStartTime = t;
|
|
config.hostStartTime = (uint64_t)(::perfetto::base::GetWallTimeNs().count());
|
|
config.guestTimeDiff = config.guestStartTime - config.hostStartTime;
|
|
});
|
|
}
|
|
|
|
|
|
} // namespace perfetto
|