1 #include "perfetto.h"
2 #include "perfetto-tracing-only.h"
3 #include "perfetto_trace.pb.h"
4
5 #include <string>
6 #include <thread>
7 #include <fstream>
8
9 PERFETTO_DEFINE_CATEGORIES(
10 ::perfetto::Category("gfx")
11 .SetDescription("Events from the graphics subsystem"));
12 PERFETTO_TRACK_EVENT_STATIC_STORAGE();
13
14 #define TRACE_COUNTER(category, name, value) \
15 PERFETTO_INTERNAL_TRACK_EVENT( \
16 category, name, \
17 ::perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER, [&](::perfetto::EventContext ctx){ \
18 ctx.event()->set_counter_value(value); \
19 })
20
21 #ifdef __cplusplus
22 # define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
23 # define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
24 #else
25 # define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
26 # define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
27 #endif
28
29 namespace virtualdeviceperfetto {
30
31 static bool sPerfettoInitialized = false;
32
33 static VirtualDeviceTraceConfig sTraceConfig = {
34 .initialized = false,
35 .tracingDisabled = true,
36 .packetsWritten = 0,
37 .sequenceIdWritten = 0,
38 .currentInterningId = 1,
39 .currentThreadId = 1,
40 .hostFilename = "vmm.trace",
41 .guestFilename = nullptr,
42 .combinedFilename = nullptr,
43 .hostStartTime = 0,
44 .guestStartTime = 0,
45 .guestTimeDiff = 0,
46 .perThreadStorageMb = 1,
47 };
48
setTraceConfig(std::function<void (VirtualDeviceTraceConfig &)> f)49 PERFETTO_TRACING_ONLY_EXPORT void setTraceConfig(std::function<void(VirtualDeviceTraceConfig&)> f) {
50 f(sTraceConfig);
51 }
52
queryTraceConfig()53 PERFETTO_TRACING_ONLY_EXPORT VirtualDeviceTraceConfig queryTraceConfig() {
54 return sTraceConfig;
55 }
56
initialize(const bool ** tracingDisabledPtr)57 PERFETTO_TRACING_ONLY_EXPORT void initialize(const bool** tracingDisabledPtr) {
58 if (!sPerfettoInitialized) {
59 ::perfetto::TracingInitArgs args;
60 args.backends |= ::perfetto::kInProcessBackend;
61 ::perfetto::Tracing::Initialize(args);
62 ::perfetto::TrackEvent::Register();
63 sPerfettoInitialized = true;
64 }
65
66 // An optimization to have faster queries of whether tracing is enabled.
67 *tracingDisabledPtr = &sTraceConfig.tracingDisabled;
68 }
69
70 static std::unique_ptr<::perfetto::TracingSession> sTracingSession;
71
useFilenameByEnv(const char * s)72 bool useFilenameByEnv(const char* s) {
73 return s && ("" != std::string(s));
74 }
75
enableTracing()76 PERFETTO_TRACING_ONLY_EXPORT void enableTracing() {
77 const char* hostFilenameByEnv = std::getenv("VPERFETTO_HOST_FILE");
78 const char* guestFilenameByEnv = std::getenv("VPERFETTO_GUEST_FILE");
79 const char* combinedFilenameByEnv = std::getenv("VPERFETTO_COMBINED_FILE");
80
81 if (useFilenameByEnv(hostFilenameByEnv)) {
82 sTraceConfig.hostFilename = hostFilenameByEnv;
83 }
84
85 if (useFilenameByEnv(guestFilenameByEnv)) {
86 sTraceConfig.guestFilename = guestFilenameByEnv;
87 }
88
89 if (useFilenameByEnv(combinedFilenameByEnv)) {
90 sTraceConfig.combinedFilename = combinedFilenameByEnv;
91 }
92
93 // Don't enable tracing if host filename is null
94 if (!sTraceConfig.hostFilename) return;
95
96 // Don't enable it twice
97 if (!sTraceConfig.tracingDisabled) return;
98
99 if (!sTracingSession) {
100 fprintf(stderr, "%s: Tracing begins================================================================================\n", __func__);
101 fprintf(stderr, "%s: Configuration:\n", __func__);
102 fprintf(stderr, "%s: host filename: %s (possibly set via $VPERFETTO_HOST_FILE)\n", __func__, sTraceConfig.hostFilename);
103 fprintf(stderr, "%s: guest filename: %s (possibly set via $VPERFETTO_GUEST_FILE)\n", __func__, sTraceConfig.guestFilename);
104 fprintf(stderr, "%s: combined filename: %s (possibly set via $VPERFETTO_COMBINED_FILE)\n", __func__, sTraceConfig.combinedFilename);
105 fprintf(stderr, "%s: guest time diff to add to host time: %llu\n", __func__, (unsigned long long)sTraceConfig.guestTimeDiff);
106
107 auto desc = ::perfetto::ProcessTrack::Current().Serialize();
108 desc.mutable_process()->set_process_name("VirtualMachineMonitorProcess");
109 ::perfetto::TrackEvent::SetTrackDescriptor(::perfetto::ProcessTrack::Current(), desc);
110
111 ::perfetto::TraceConfig cfg;
112 ::perfetto::protos::gen::TrackEventConfig track_event_cfg;
113 cfg.add_buffers()->set_size_kb(1024 * 100); // Record up to 100 MiB.
114 auto* ds_cfg = cfg.add_data_sources()->mutable_config();
115 ds_cfg->set_name("track_event");
116 ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString());
117
118 // Disable service events in the host trace, because they interfere
119 // with the guest's and we end up dropping packets on one side or the other.
120 auto* builtin_ds_cfg = cfg.mutable_builtin_data_sources();
121 builtin_ds_cfg->set_disable_service_events(true);
122
123 sTracingSession = ::perfetto::Tracing::NewTrace();
124 sTracingSession->Setup(cfg);
125 sTracingSession->StartBlocking();
126 sTraceConfig.tracingDisabled = false;
127 }
128 }
129
asyncTraceSaveFunc()130 void asyncTraceSaveFunc() {
131 fprintf(stderr, "%s: Saving combined trace async...\n", __func__);
132
133 static const int kWaitSecondsPerIteration = 1;
134 static const int kMaxIters = 20;
135 static const int kMinItersForGuestFileSize = 2;
136
137 const char* hostFilename = sTraceConfig.hostFilename;
138 const char* guestFilename = sTraceConfig.guestFilename;
139 const char* combinedFilename = sTraceConfig.combinedFilename;
140
141 std::streampos currGuestSize = 0;
142 int numGoodGuestFileSizeIters = 0;
143 bool good = false;
144
145 for (int i = 0; i < kMaxIters; ++i) {
146 fprintf(stderr, "%s: Waiting for 1 second...\n", __func__);
147 std::this_thread::sleep_for(std::chrono::seconds(kWaitSecondsPerIteration));
148 fprintf(stderr, "%s: Querying file size of guest trace...\n", __func__);
149 std::ifstream guestFile(guestFilename, std::ios::in | std::ios::binary | std::ios::ate);
150 std::streampos size = guestFile.tellg();
151
152 if (!size) {
153 fprintf(stderr, "%s: No size, try again\n", __func__);
154 continue;
155 }
156
157 if (size != currGuestSize) {
158 fprintf(stderr, "%s: Sized changed (%llu to %llu), try again\n", __func__,
159 (unsigned long long)currGuestSize, (unsigned long long)size);
160 currGuestSize = size;
161 continue;
162 }
163
164 ++numGoodGuestFileSizeIters;
165
166 if (numGoodGuestFileSizeIters == kMinItersForGuestFileSize) {
167 fprintf(stderr, "%s: size is stable, continue saving\n", __func__);
168 good = true;
169 break;
170 }
171 }
172
173 if (!good) {
174 fprintf(stderr, "%s: Timed out when waiting for guest file to stabilize, skipping combined trace saving.\n", __func__);
175 return;
176 }
177
178 std::ifstream hostFile(hostFilename, std::ios_base::binary);
179 std::ifstream guestFile(guestFilename, std::ios_base::binary);
180 std::ofstream combinedFile(combinedFilename, std::ios::out | std::ios_base::binary);
181
182 combinedFile << guestFile.rdbuf() << hostFile.rdbuf();
183
184 fprintf(stderr, "%s: Wrote combined trace (%s)\n", __func__, combinedFilename);
185 }
186
187 template <typename T>
varIntEncodingSize(T value)188 static inline size_t varIntEncodingSize(T value) {
189 // If value is <= 0 we must first sign extend to int64_t (see [1]).
190 // Finally we always cast to an unsigned value to to avoid arithmetic
191 // (sign expanding) shifts in the while loop.
192 // [1]: "If you use int32 or int64 as the type for a negative number, the
193 // resulting varint is always ten bytes long".
194 // - developers.google.com/protocol-buffers/docs/encoding
195 // So for each input type we do the following casts:
196 // uintX_t -> uintX_t -> uintX_t
197 // int8_t -> int64_t -> uint64_t
198 // int16_t -> int64_t -> uint64_t
199 // int32_t -> int64_t -> uint64_t
200 // int64_t -> int64_t -> uint64_t
201 using MaybeExtendedType =
202 typename std::conditional<std::is_unsigned<T>::value, T, int64_t>::type;
203 using UnsignedType = typename std::make_unsigned<MaybeExtendedType>::type;
204
205 MaybeExtendedType extended_value = static_cast<MaybeExtendedType>(value);
206 UnsignedType unsigned_value = static_cast<UnsignedType>(extended_value);
207
208 size_t bytes = 0;
209
210 while (unsigned_value >= 0x80) {
211 ++bytes;
212 unsigned_value >>= 7;
213 }
214
215 return bytes + 1;
216 }
217
218 template <typename T>
writeVarInt(T value,uint8_t * target)219 static inline uint8_t* writeVarInt(T value, uint8_t* target) {
220 // If value is <= 0 we must first sign extend to int64_t (see [1]).
221 // Finally we always cast to an unsigned value to to avoid arithmetic
222 // (sign expanding) shifts in the while loop.
223 // [1]: "If you use int32 or int64 as the type for a negative number, the
224 // resulting varint is always ten bytes long".
225 // - developers.google.com/protocol-buffers/docs/encoding
226 // So for each input type we do the following casts:
227 // uintX_t -> uintX_t -> uintX_t
228 // int8_t -> int64_t -> uint64_t
229 // int16_t -> int64_t -> uint64_t
230 // int32_t -> int64_t -> uint64_t
231 // int64_t -> int64_t -> uint64_t
232 using MaybeExtendedType =
233 typename std::conditional<std::is_unsigned<T>::value, T, int64_t>::type;
234 using UnsignedType = typename std::make_unsigned<MaybeExtendedType>::type;
235
236 MaybeExtendedType extended_value = static_cast<MaybeExtendedType>(value);
237 UnsignedType unsigned_value = static_cast<UnsignedType>(extended_value);
238
239 while (unsigned_value >= 0x80) {
240 *target++ = static_cast<uint8_t>(unsigned_value) | 0x80;
241 unsigned_value >>= 7;
242 }
243 *target = static_cast<uint8_t>(unsigned_value);
244 return target + 1;
245 }
246
sProcessTrace(const std::vector<char> & trace)247 static std::vector<char> sProcessTrace(const std::vector<char>& trace) {
248 std::vector<char> res;
249
250 ::perfetto::protos::Trace pbtrace;
251 std::string traceStr(trace.begin(), trace.end());
252
253 if (pbtrace.ParseFromString(traceStr)) {
254 } else {
255 fprintf(stderr, "%s: Failed to parse protobuf as a string\n", __func__);
256 return res;
257 }
258
259 fprintf(stderr, "%s: postprocessing trace with guest time diff of %llu\n", __func__,
260 (unsigned long long)sTraceConfig.guestTimeDiff);
261
262 for (int i = 0; i < pbtrace.packet_size(); ++i) {
263 // Process one trace packet.
264 auto* packet = pbtrace.mutable_packet(i);
265 if (packet->has_timestamp()) {
266 packet->set_timestamp(packet->timestamp() + sTraceConfig.guestTimeDiff);
267 }
268 }
269
270 std::string traceAfter;
271 pbtrace.SerializeToString(&traceAfter);
272
273 std::vector<char> res2(traceAfter.begin(), traceAfter.end());
274 return res2;
275 }
276
disableTracing()277 PERFETTO_TRACING_ONLY_EXPORT void disableTracing() {
278 if (sTracingSession) {
279 sTraceConfig.tracingDisabled = true;
280 sTracingSession->StopBlocking();
281 std::vector<char> trace_data(sTracingSession->ReadTraceBlocking());
282
283 std::vector<char> processed =
284 sProcessTrace(trace_data);
285
286 fprintf(stderr, "%s: Tracing ended================================================================================\n", __func__);
287 fprintf(stderr, "%s: Saving trace to disk. Configuration:\n", __func__);
288 fprintf(stderr, "%s: host filename: %s\n", __func__, sTraceConfig.hostFilename);
289 fprintf(stderr, "%s: guest filename: %s\n", __func__, sTraceConfig.guestFilename);
290 fprintf(stderr, "%s: combined filename: %s\n", __func__, sTraceConfig.combinedFilename);
291
292 fprintf(stderr, "%s: Saving host trace first...\n", __func__);
293
294 // Write the trace into a file.
295 std::ofstream output;
296 output.open(sTraceConfig.hostFilename, std::ios::out | std::ios::binary);
297 output.write(&processed[0], processed.size());
298 output.close();
299 sTracingSession.reset();
300
301 fprintf(stderr, "%s: Saving host trace first...(done)\n", __func__);
302
303 if (!sTraceConfig.guestFilename || !sTraceConfig.combinedFilename) {
304 fprintf(stderr, "%s: skipping guest combined trace, "
305 "either guest file name (%p) not specified or "
306 "combined file name (%p) not specified\n", __func__,
307 sTraceConfig.guestFilename,
308 sTraceConfig.combinedFilename);
309 return;
310 }
311
312 std::thread saveThread(asyncTraceSaveFunc);
313 saveThread.detach();
314 }
315 }
316
beginTrace(const char * eventName)317 PERFETTO_TRACING_ONLY_EXPORT void beginTrace(const char* eventName) {
318 TRACE_EVENT_BEGIN("gfx", ::perfetto::StaticString{eventName});
319 }
320
endTrace()321 PERFETTO_TRACING_ONLY_EXPORT void endTrace() {
322 TRACE_EVENT_END("gfx");
323 }
324
traceCounter(const char * name,int64_t value)325 PERFETTO_TRACING_ONLY_EXPORT void traceCounter(const char* name, int64_t value) {
326 // TODO: What this really needs until its supported in the official sdk:
327 // a. a static global to track uuids and names for counters
328 // b. track objects generated dynamically
329 // c. setting the descriptor of these track objects
330 // if (CC_LIKELY(sTraceConfig.tracingDisabled)) return;
331 // TRACE_COUNTER("gfx", ::perfetto::StaticString{name}, value);
332 }
333
setGuestTime(uint64_t t)334 PERFETTO_TRACING_ONLY_EXPORT void setGuestTime(uint64_t t) {
335 virtualdeviceperfetto::setTraceConfig([t](virtualdeviceperfetto::VirtualDeviceTraceConfig& config) {
336 // can only be set before tracing
337 if (!config.tracingDisabled) {
338 return;
339 }
340 config.guestStartTime = t;
341 config.hostStartTime = (uint64_t)(::perfetto::base::GetWallTimeNs().count());
342 config.guestTimeDiff = config.guestStartTime - config.hostStartTime;
343 });
344 }
345
346
347 } // namespace perfetto
348