Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ChromeTracingTracer.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
10#include "src/core/SkOSFile.h"
13#include "src/utils/SkOSPath.h"
15
16#include <chrono>
17
18using namespace skia_private;
19
20namespace {
21
22/**
23 * All events have a fixed block of information (TraceEvent), plus variable length payload:
24 * {TraceEvent} {TraceEventArgs} {Inline Payload}
25 */
26struct TraceEventArg {
27 uint8_t fArgType;
28 const char* fArgName;
29 uint64_t fArgValue;
30};
31
32// These fields are ordered to minimize size due to alignment. Argument types could be packed
33// better, but very few events have many arguments, so the net loss is pretty small.
34struct TraceEvent {
35 char fPhase;
36 uint8_t fNumArgs;
37 uint32_t fSize;
38
39 const char* fName;
40 // TODO: Merge fID and fClockEnd (never used together)
41 uint64_t fID;
42 uint64_t fClockBegin;
43 uint64_t fClockEnd;
44 SkThreadID fThreadID;
45
46 TraceEvent* next() {
47 return reinterpret_cast<TraceEvent*>(reinterpret_cast<char*>(this) + fSize);
48 }
49 TraceEventArg* args() { return reinterpret_cast<TraceEventArg*>(this + 1); }
50 char* stringTable() { return reinterpret_cast<char*>(this->args() + fNumArgs); }
51};
52
53} // namespace
54
55ChromeTracingTracer::ChromeTracingTracer(const char* filename) : fFilename(filename) {
56 this->createBlock();
57}
58
60
61void ChromeTracingTracer::createBlock() {
62 fCurBlock.fBlock = BlockPtr(new uint8_t[kBlockSize]);
63 fCurBlock.fEventsInBlock = 0;
64 fCurBlockUsed = 0;
65}
66
67SkEventTracer::Handle ChromeTracingTracer::appendEvent(const void* data, size_t size) {
68 SkASSERT(size > 0 && size <= kBlockSize);
69
70 SkAutoSpinlock lock(fMutex);
71 if (fCurBlockUsed + size > kBlockSize) {
72 fBlocks.push_back(std::move(fCurBlock));
73 this->createBlock();
74 }
75 memcpy(fCurBlock.fBlock.get() + fCurBlockUsed, data, size);
76 Handle handle = reinterpret_cast<Handle>(fCurBlock.fBlock.get() + fCurBlockUsed);
77 fCurBlockUsed += size;
78 fCurBlock.fEventsInBlock++;
79 return handle;
80}
81
83 const uint8_t* categoryEnabledFlag,
84 const char* name,
85 uint64_t id,
86 int numArgs,
87 const char** argNames,
88 const uint8_t* argTypes,
89 const uint64_t* argValues,
90 uint8_t flags) {
91 // TODO: Respect flags (or assert). INSTANT events encode scope in flags, should be stored
92 // using "s" key in JSON. COPY flag should be supported or rejected.
93
94 // Figure out how much extra storage we need for copied strings
95 int size = static_cast<int>(sizeof(TraceEvent) + numArgs * sizeof(TraceEventArg));
96 for (int i = 0; i < numArgs; ++i) {
97 if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
98 size += strlen(skia_private::TraceValueAsString(argValues[i])) + 1;
99 }
100 }
101
102 size = SkAlign8(size);
103
105 uint8_t* storagePtr = storage.push_back_n(size);
106
107 TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(storagePtr);
108 traceEvent->fPhase = phase;
109 traceEvent->fNumArgs = numArgs;
110 traceEvent->fSize = size;
111 traceEvent->fName = name;
112 traceEvent->fID = id;
113 traceEvent->fClockBegin = std::chrono::steady_clock::now().time_since_epoch().count();
114 traceEvent->fClockEnd = 0;
115 traceEvent->fThreadID = SkGetThreadID();
116
117 TraceEventArg* traceEventArgs = traceEvent->args();
118 char* stringTableBase = traceEvent->stringTable();
119 char* stringTable = stringTableBase;
120 for (int i = 0; i < numArgs; ++i) {
121 traceEventArgs[i].fArgName = argNames[i];
122 traceEventArgs[i].fArgType = argTypes[i];
123 if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
124 // Just write an offset into the arguments array
125 traceEventArgs[i].fArgValue = stringTable - stringTableBase;
126
127 // Copy string into our buffer (and advance)
128 const char* valueStr = skia_private::TraceValueAsString(argValues[i]);
129 while (*valueStr) {
130 *stringTable++ = *valueStr++;
131 }
132 *stringTable++ = 0;
133 } else {
134 traceEventArgs[i].fArgValue = argValues[i];
135 }
136 }
137
138 return this->appendEvent(storagePtr, size);
139}
140
141void ChromeTracingTracer::updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
142 const char* name,
143 SkEventTracer::Handle handle) {
144 // We could probably get away with not locking here, but let's be totally safe.
145 SkAutoSpinlock lock(fMutex);
146 TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(handle);
147 traceEvent->fClockEnd = std::chrono::steady_clock::now().time_since_epoch().count();
148}
149
151 uint64_t argValue,
152 uint8_t argType,
153 const char* stringTableBase) {
154 switch (argType) {
155 case TRACE_VALUE_TYPE_BOOL: writer->appendBool(argValue); break;
156 case TRACE_VALUE_TYPE_UINT: writer->appendU64(argValue); break;
157 case TRACE_VALUE_TYPE_INT: writer->appendS64(static_cast<int64_t>(argValue)); break;
158 case TRACE_VALUE_TYPE_DOUBLE: writer->appendDouble(sk_bit_cast<double>(argValue)); break;
161 break;
164 break;
166 // See addTraceEvent(), the value in _COPY_STRING events is replaced with an offset
167 writer->appendCString(stringTableBase + argValue);
168 break;
169 default: writer->appendNString("<unknown type>"); break;
170 }
171}
172
173namespace {
174
175struct TraceEventSerializationState {
176 TraceEventSerializationState(uint64_t clockOffset)
177 : fClockOffset(clockOffset), fNextThreadID(0) {}
178
179 int getShortThreadID(SkThreadID id) {
180 if (int* shortIDPtr = fShortThreadIDMap.find(id)) {
181 return *shortIDPtr;
182 }
183 int shortID = fNextThreadID++;
184 fShortThreadIDMap.set(id, shortID);
185 return shortID;
186 }
187
188 uint64_t fClockOffset;
189 THashMap<uint64_t, const char*> fBaseTypeResolver;
190 int fNextThreadID;
191 THashMap<SkThreadID, int> fShortThreadIDMap;
192};
193
194} // namespace
195
197 TraceEvent* traceEvent,
198 TraceEventSerializationState* serializationState) {
199 // We track the original (creation time) "name" of each currently live object, so we can
200 // automatically insert "base_name" fields in object snapshot events.
201 auto baseTypeResolver = &(serializationState->fBaseTypeResolver);
202 if (TRACE_EVENT_PHASE_CREATE_OBJECT == traceEvent->fPhase) {
203 SkASSERT(nullptr == baseTypeResolver->find(traceEvent->fID));
204 baseTypeResolver->set(traceEvent->fID, traceEvent->fName);
205 } else if (TRACE_EVENT_PHASE_DELETE_OBJECT == traceEvent->fPhase) {
206 SkASSERT(nullptr != baseTypeResolver->find(traceEvent->fID));
207 baseTypeResolver->remove(traceEvent->fID);
208 }
209
210 writer->beginObject();
211
212 writer->appendString("ph", &traceEvent->fPhase, 1);
213 writer->appendCString("name", traceEvent->fName);
214 if (0 != traceEvent->fID) {
215 // IDs are (almost) always pointers
216 writer->appendPointer("id", reinterpret_cast<void*>(traceEvent->fID));
217 }
218
219 // Offset timestamps to reduce JSON length, then convert nanoseconds to microseconds
220 // (standard time unit for tracing JSON files).
221 uint64_t relativeTimestamp =
222 static_cast<int64_t>(traceEvent->fClockBegin - serializationState->fClockOffset);
223 writer->appendDouble("ts", static_cast<double>(relativeTimestamp) * 1E-3);
224 if (0 != traceEvent->fClockEnd) {
225 double dur = static_cast<double>(traceEvent->fClockEnd - traceEvent->fClockBegin) * 1E-3;
226 writer->appendDouble("dur", dur);
227 }
228
229 writer->appendS64("tid", serializationState->getShortThreadID(traceEvent->fThreadID));
230 // Trace events *must* include a process ID, but for internal tools this isn't particularly
231 // important (and certainly not worth adding a cross-platform API to get it).
232 writer->appendS32("pid", 0);
233
234 if (traceEvent->fNumArgs) {
235 writer->beginObject("args");
236 const char* stringTable = traceEvent->stringTable();
237 bool addedSnapshot = false;
238 if (TRACE_EVENT_PHASE_SNAPSHOT_OBJECT == traceEvent->fPhase &&
239 baseTypeResolver->find(traceEvent->fID) &&
240 0 != strcmp(*baseTypeResolver->find(traceEvent->fID), traceEvent->fName)) {
241 // Special handling for snapshots where the name differs from creation.
242 writer->beginObject("snapshot");
243 writer->appendCString("base_type", *baseTypeResolver->find(traceEvent->fID));
244 addedSnapshot = true;
245 }
246
247 for (int i = 0; i < traceEvent->fNumArgs; ++i) {
248 const TraceEventArg* arg = traceEvent->args() + i;
249 // TODO: Skip '#'
250 writer->appendName(arg->fArgName);
251
252 if (arg->fArgName && '#' == arg->fArgName[0]) {
253 writer->beginObject();
254 writer->appendName("id_ref");
255 trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
256 writer->endObject();
257 } else {
258 trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
259 }
260 }
261
262 if (addedSnapshot) {
263 writer->endObject();
264 }
265
266 writer->endObject();
267 }
268
269 writer->endObject();
270}
271
272void ChromeTracingTracer::flush() {
273 SkAutoSpinlock lock(fMutex);
274
275 SkString dirname = SkOSPath::Dirname(fFilename.c_str());
276 if (!dirname.isEmpty() && !sk_exists(dirname.c_str(), kWrite_SkFILE_Flag)) {
277 if (!sk_mkdir(dirname.c_str())) {
278 SkDebugf("Failed to create directory.");
279 }
280 }
281
282 SkFILEWStream fileStream(fFilename.c_str());
283 SkJSONWriter writer(&fileStream, SkJSONWriter::Mode::kFast);
284 writer.beginArray();
285
286 uint64_t clockOffset = 0;
287 if (fBlocks.size() > 0) {
288 clockOffset = reinterpret_cast<TraceEvent*>(fBlocks[0].fBlock.get())->fClockBegin;
289 } else if (fCurBlock.fEventsInBlock > 0) {
290 clockOffset = reinterpret_cast<TraceEvent*>(fCurBlock.fBlock.get())->fClockBegin;
291 }
292
293 TraceEventSerializationState serializationState(clockOffset);
294
295 auto event_block_to_json = [](SkJSONWriter* writer,
296 const TraceEventBlock& block,
297 TraceEventSerializationState* serializationState) {
298 TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(block.fBlock.get());
299 for (int i = 0; i < block.fEventsInBlock; ++i) {
300 trace_event_to_json(writer, traceEvent, serializationState);
301 traceEvent = traceEvent->next();
302 }
303 };
304
305 for (int i = 0; i < fBlocks.size(); ++i) {
306 event_block_to_json(&writer, fBlocks[i], &serializationState);
307 }
308 event_block_to_json(&writer, fCurBlock, &serializationState);
309
310 writer.endArray();
311 writer.flush();
312 fileStream.flush();
313}
static void trace_event_to_json(SkJSONWriter *writer, TraceEvent *traceEvent, TraceEventSerializationState *serializationState)
static void trace_value_to_json(SkJSONWriter *writer, uint64_t argValue, uint8_t argType, const char *stringTableBase)
const char * fName
static float next(float f)
static constexpr T SkAlign8(T x)
Definition SkAlign.h:17
#define SkASSERT(cond)
Definition SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
bool sk_mkdir(const char *path)
bool sk_exists(const char *path, SkFILE_Flags=(SkFILE_Flags) 0)
@ kWrite_SkFILE_Flag
Definition SkOSFile.h:21
SkThreadID SkGetThreadID()
int64_t SkThreadID
Definition SkThreadID.h:16
#define TRACE_VALUE_TYPE_STRING
#define TRACE_VALUE_TYPE_BOOL
#define TRACE_VALUE_TYPE_POINTER
#define TRACE_VALUE_TYPE_COPY_STRING
#define TRACE_VALUE_TYPE_INT
#define TRACE_VALUE_TYPE_UINT
#define TRACE_VALUE_TYPE_DOUBLE
#define TRACE_EVENT_PHASE_SNAPSHOT_OBJECT
#define TRACE_EVENT_PHASE_CREATE_OBJECT
#define TRACE_EVENT_PHASE_DELETE_OBJECT
ChromeTracingTracer(const char *filename)
SkEventTracer::Handle addTraceEvent(char phase, const uint8_t *categoryEnabledFlag, const char *name, uint64_t id, int numArgs, const char **argNames, const uint8_t *argTypes, const uint64_t *argValues, uint8_t flags) override
void updateTraceEventDuration(const uint8_t *categoryEnabledFlag, const char *name, SkEventTracer::Handle handle) override
uint64_t Handle
void appendS32(int32_t value)
void appendNString(char const (&value)[N])
void appendDouble(double value)
void beginObject(const char *name=nullptr, bool multiline=true)
void appendU64(uint64_t value)
void appendPointer(const void *value)
void appendS64(int64_t value)
void appendBool(bool value)
void appendName(const char *name)
void appendCString(const char *value)
void appendString(const char *value, size_t size)
static SkString Dirname(const char *fullPath)
Definition SkOSPath.cpp:36
bool isEmpty() const
Definition SkString.h:130
const char * c_str() const
Definition SkString.h:133
T * push_back_n(int n)
Definition SkTArray.h:262
int size() const
Definition SkTArray.h:416
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
const char * name
Definition fuchsia.cc:50
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
void TraceEvent(TraceArg category, TraceArg name, size_t flow_id_count, const uint64_t *flow_ids, Args... args)
static const void * TraceValueAsPointer(uint64_t value)
static const char * TraceValueAsString(uint64_t value)
const uintptr_t id