1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "vm/TraceLoggingGraph.h"
8
9 #ifdef XP_WIN
10 #include <process.h>
11 #define getpid _getpid
12 #else
13 #include <unistd.h>
14 #endif
15
16 #include "mozilla/EndianUtils.h"
17 #include "mozilla/ScopeExit.h"
18
19 #include "jsstr.h"
20
21 #include "js/UniquePtr.h"
22 #include "threading/LockGuard.h"
23 #include "threading/Thread.h"
24 #include "vm/TraceLogging.h"
25
26 #ifndef DEFAULT_TRACE_LOG_DIR
27 # if defined(_WIN32)
28 # define DEFAULT_TRACE_LOG_DIR "."
29 # else
30 # define DEFAULT_TRACE_LOG_DIR "/tmp/"
31 # endif
32 #endif
33
34 using mozilla::MakeScopeExit;
35 using mozilla::NativeEndian;
36
37 TraceLoggerGraphState* traceLoggerGraphState = nullptr;
38
39 // gcc and clang have these in symcat.h, but MSVC does not.
40 #ifndef STRINGX
41 # define STRINGX(x) #x
42 #endif
43 #ifndef XSTRING
44 # define XSTRING(macro) STRINGX(macro)
45 #endif
46
47 #define MAX_LOGGERS 999
48
49 // Return a filename relative to the output directory. %u and %d substitutions
50 // are allowed, with %u standing for a full 32-bit number and %d standing for
51 // an up to 3-digit number.
52 static js::UniqueChars
53 MOZ_FORMAT_PRINTF(1, 2)
AllocTraceLogFilename(const char * pattern,...)54 AllocTraceLogFilename(const char* pattern, ...) {
55 js::UniqueChars filename;
56
57 va_list ap;
58
59 static const char* outdir = getenv("TLDIR") ? getenv("TLDIR") : DEFAULT_TRACE_LOG_DIR;
60 size_t len = strlen(outdir) + 1; // "+ 1" is for the '/'
61
62 for (const char* p = pattern; *p; p++) {
63 if (*p == '%') {
64 p++;
65 if (*p == 'u')
66 len += sizeof("4294967295") - 1;
67 else if (*p == 'd')
68 len += sizeof(XSTRING(MAX_LOGGERS)) - 1;
69 else
70 MOZ_CRASH("Invalid format");
71 } else {
72 len++;
73 }
74 }
75
76 len++; // For the terminating NUL.
77
78 filename.reset((char*) js_malloc(len));
79 if (!filename)
80 return nullptr;
81 char* rest = filename.get() + sprintf(filename.get(), "%s/", outdir);
82
83 va_start(ap, pattern);
84 int ret = vsnprintf(rest, len, pattern, ap);
85 va_end(ap);
86 if (ret < 0)
87 return nullptr;
88
89 MOZ_ASSERT(size_t(ret) <= len - (strlen(outdir) + 1),
90 "overran TL filename buffer; %d given too large a value?");
91
92 return filename;
93 }
94
95 bool
init()96 TraceLoggerGraphState::init()
97 {
98 pid_ = (uint32_t) getpid();
99
100 js::UniqueChars filename = AllocTraceLogFilename("tl-data.%u.json", pid_);
101 out = fopen(filename.get(), "w");
102 if (!out) {
103 fprintf(stderr, "warning: failed to create TraceLogger output file %s\n", filename.get());
104 return false;
105 }
106
107 fprintf(out, "[");
108
109 // Write the latest tl-data.*.json file to tl-data.json.
110 // In most cases that is the wanted file.
111 js::UniqueChars masterFilename = AllocTraceLogFilename("tl-data.json");
112 if (FILE* last = fopen(masterFilename.get(), "w")) {
113 char *basename = strrchr(filename.get(), '/');
114 basename = basename ? basename + 1 : filename.get();
115 fprintf(last, "\"%s\"", basename);
116 fclose(last);
117 }
118
119 #ifdef DEBUG
120 initialized = true;
121 #endif
122 return true;
123 }
124
~TraceLoggerGraphState()125 TraceLoggerGraphState::~TraceLoggerGraphState()
126 {
127 if (out) {
128 fprintf(out, "]");
129 fclose(out);
130 out = nullptr;
131 }
132
133 #ifdef DEBUG
134 initialized = false;
135 #endif
136 }
137
138 uint32_t
nextLoggerId()139 TraceLoggerGraphState::nextLoggerId()
140 {
141 js::LockGuard<js::Mutex> guard(lock);
142
143 MOZ_ASSERT(initialized);
144
145 if (numLoggers > MAX_LOGGERS) {
146 fputs("TraceLogging: Can't create more than " XSTRING(MAX_LOGGERS) " different loggers.",
147 stderr);
148 return uint32_t(-1);
149 }
150
151 if (numLoggers > 0) {
152 int written = fprintf(out, ",\n");
153 if (written < 0) {
154 fprintf(stderr, "TraceLogging: Error while writing.\n");
155 return uint32_t(-1);
156 }
157 }
158
159 int written = fprintf(out, "{\"tree\":\"tl-tree.%u.%d.tl\", \"events\":\"tl-event.%u.%d.tl\", "
160 "\"dict\":\"tl-dict.%u.%d.json\", \"treeFormat\":\"64,64,31,1,32\"",
161 pid_, numLoggers, pid_, numLoggers, pid_, numLoggers);
162
163 if (written > 0) {
164 char threadName[16];
165 js::ThisThread::GetName(threadName, sizeof(threadName));
166 if (threadName[0])
167 written = fprintf(out, ", \"threadName\":\"%s\"", threadName);
168 }
169
170 if (written > 0)
171 written = fprintf(out, "}");
172
173 if (written < 0) {
174 fprintf(stderr, "TraceLogging: Error while writing.\n");
175 return uint32_t(-1);
176 }
177
178 return numLoggers++;
179 }
180
181 static bool
EnsureTraceLoggerGraphState()182 EnsureTraceLoggerGraphState()
183 {
184 if (MOZ_LIKELY(traceLoggerGraphState))
185 return true;
186
187 traceLoggerGraphState = js_new<TraceLoggerGraphState>();
188 if (!traceLoggerGraphState)
189 return false;
190
191 if (!traceLoggerGraphState->init()) {
192 js::DestroyTraceLoggerGraphState();
193 return false;
194 }
195
196 return true;
197 }
198
199 void
DestroyTraceLoggerGraphState()200 js::DestroyTraceLoggerGraphState()
201 {
202 if (traceLoggerGraphState) {
203 js_delete(traceLoggerGraphState);
204 traceLoggerGraphState = nullptr;
205 }
206 }
207
208 bool
init(uint64_t startTimestamp)209 TraceLoggerGraph::init(uint64_t startTimestamp)
210 {
211 auto fail = MakeScopeExit([&] { failed = true; });
212
213 if (!tree.init())
214 return false;
215 if (!stack.init())
216 return false;
217
218 if (!EnsureTraceLoggerGraphState())
219 return false;
220
221 uint32_t loggerId = traceLoggerGraphState->nextLoggerId();
222 if (loggerId == uint32_t(-1))
223 return false;
224
225 uint32_t pid = traceLoggerGraphState->pid();
226
227 js::UniqueChars dictFilename = AllocTraceLogFilename("tl-dict.%u.%d.json", pid, loggerId);
228 dictFile = fopen(dictFilename.get(), "w");
229 if (!dictFile)
230 return false;
231 auto cleanupDict = MakeScopeExit([&] { fclose(dictFile); dictFile = nullptr; });
232
233 js::UniqueChars treeFilename = AllocTraceLogFilename("tl-tree.%u.%d.tl", pid, loggerId);
234 treeFile = fopen(treeFilename.get(), "w+b");
235 if (!treeFile)
236 return false;
237 auto cleanupTree = MakeScopeExit([&] { fclose(treeFile); treeFile = nullptr; });
238
239 js::UniqueChars eventFilename = AllocTraceLogFilename("tl-event.%u.%d.tl", pid, loggerId);
240 eventFile = fopen(eventFilename.get(), "wb");
241 if (!eventFile)
242 return false;
243 auto cleanupEvent = MakeScopeExit([&] { fclose(eventFile); eventFile = nullptr; });
244
245 // Create the top tree node and corresponding first stack item.
246 TreeEntry& treeEntry = tree.pushUninitialized();
247 treeEntry.setStart(startTimestamp);
248 treeEntry.setStop(0);
249 treeEntry.setTextId(0);
250 treeEntry.setHasChildren(false);
251 treeEntry.setNextId(0);
252
253 StackEntry& stackEntry = stack.pushUninitialized();
254 stackEntry.setTreeId(0);
255 stackEntry.setLastChildId(0);
256 stackEntry.setActive(true);
257
258 if (fprintf(dictFile, "[") < 0) {
259 fprintf(stderr, "TraceLogging: Error while writing.\n");
260 return false;
261 }
262
263 fail.release();
264 cleanupDict.release();
265 cleanupTree.release();
266 cleanupEvent.release();
267
268 return true;
269 }
270
~TraceLoggerGraph()271 TraceLoggerGraph::~TraceLoggerGraph()
272 {
273 // Write dictionary to disk
274 if (dictFile) {
275 int written = fprintf(dictFile, "]");
276 if (written < 0)
277 fprintf(stderr, "TraceLogging: Error while writing.\n");
278 fclose(dictFile);
279
280 dictFile = nullptr;
281 }
282
283 if (!failed && treeFile) {
284 // Make sure every start entry has a corresponding stop value.
285 // We temporarily enable logging for this. Stop doesn't need any extra data,
286 // so is safe to do even when we have encountered OOM.
287 enabled = true;
288 while (stack.size() > 1)
289 stopEvent(0);
290 enabled = false;
291 }
292
293 if (!failed && !flush()) {
294 fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n");
295 enabled = false;
296 failed = true;
297 }
298
299 if (treeFile) {
300 fclose(treeFile);
301 treeFile = nullptr;
302 }
303
304 if (eventFile) {
305 fclose(eventFile);
306 eventFile = nullptr;
307 }
308 }
309
310 bool
flush()311 TraceLoggerGraph::flush()
312 {
313 MOZ_ASSERT(!failed);
314
315 if (treeFile) {
316 // Format data in big endian.
317 for (size_t i = 0; i < tree.size(); i++)
318 entryToBigEndian(&tree[i]);
319
320 int success = fseek(treeFile, 0, SEEK_END);
321 if (success != 0)
322 return false;
323
324 size_t bytesWritten = fwrite(tree.data(), sizeof(TreeEntry), tree.size(), treeFile);
325 if (bytesWritten < tree.size())
326 return false;
327
328 treeOffset += tree.size();
329 tree.clear();
330 }
331
332 return true;
333 }
334
335 void
entryToBigEndian(TreeEntry * entry)336 TraceLoggerGraph::entryToBigEndian(TreeEntry* entry)
337 {
338 entry->start_ = NativeEndian::swapToBigEndian(entry->start_);
339 entry->stop_ = NativeEndian::swapToBigEndian(entry->stop_);
340 uint32_t data = (entry->u.s.textId_ << 1) + entry->u.s.hasChildren_;
341 entry->u.value_ = NativeEndian::swapToBigEndian(data);
342 entry->nextId_ = NativeEndian::swapToBigEndian(entry->nextId_);
343 }
344
345 void
entryToSystemEndian(TreeEntry * entry)346 TraceLoggerGraph::entryToSystemEndian(TreeEntry* entry)
347 {
348 entry->start_ = NativeEndian::swapFromBigEndian(entry->start_);
349 entry->stop_ = NativeEndian::swapFromBigEndian(entry->stop_);
350
351 uint32_t data = NativeEndian::swapFromBigEndian(entry->u.value_);
352 entry->u.s.textId_ = data >> 1;
353 entry->u.s.hasChildren_ = data & 0x1;
354
355 entry->nextId_ = NativeEndian::swapFromBigEndian(entry->nextId_);
356 }
357
358 void
startEvent(uint32_t id,uint64_t timestamp)359 TraceLoggerGraph::startEvent(uint32_t id, uint64_t timestamp)
360 {
361 if (failed || enabled == 0)
362 return;
363
364 if (!tree.hasSpaceForAdd()) {
365 if (tree.size() >= treeSizeFlushLimit() || !tree.ensureSpaceBeforeAdd()) {
366 if (!flush()) {
367 fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n");
368 enabled = false;
369 failed = true;
370 return;
371 }
372 }
373 }
374
375 if (!startEventInternal(id, timestamp)) {
376 fprintf(stderr, "TraceLogging: Failed to start an event.\n");
377 enabled = false;
378 failed = true;
379 return;
380 }
381 }
382
383 TraceLoggerGraph::StackEntry&
getActiveAncestor()384 TraceLoggerGraph::getActiveAncestor()
385 {
386 uint32_t parentId = stack.lastEntryId();
387 while (!stack[parentId].active())
388 parentId--;
389 return stack[parentId];
390 }
391
392 bool
startEventInternal(uint32_t id,uint64_t timestamp)393 TraceLoggerGraph::startEventInternal(uint32_t id, uint64_t timestamp)
394 {
395 if (!stack.ensureSpaceBeforeAdd())
396 return false;
397
398 // Patch up the tree to be correct. There are two scenarios:
399 // 1) Parent has no children yet. So update parent to include children.
400 // 2) Parent has already children. Update last child to link to the new
401 // child.
402 StackEntry& parent = getActiveAncestor();
403 #ifdef DEBUG
404 TreeEntry entry;
405 if (!getTreeEntry(parent.treeId(), &entry))
406 return false;
407 #endif
408
409 if (parent.lastChildId() == 0) {
410 MOZ_ASSERT(!entry.hasChildren());
411 MOZ_ASSERT(parent.treeId() == treeOffset + tree.size() - 1);
412
413 if (!updateHasChildren(parent.treeId()))
414 return false;
415 } else {
416 MOZ_ASSERT(entry.hasChildren());
417
418 if (!updateNextId(parent.lastChildId(), tree.size() + treeOffset))
419 return false;
420 }
421
422 // Add a new tree entry.
423 TreeEntry& treeEntry = tree.pushUninitialized();
424 treeEntry.setStart(timestamp);
425 treeEntry.setStop(0);
426 treeEntry.setTextId(id);
427 treeEntry.setHasChildren(false);
428 treeEntry.setNextId(0);
429
430 // Add a new stack entry.
431 StackEntry& stackEntry = stack.pushUninitialized();
432 stackEntry.setTreeId(tree.lastEntryId() + treeOffset);
433 stackEntry.setLastChildId(0);
434 stackEntry.setActive(true);
435
436 // Set the last child of the parent to this newly added entry.
437 parent.setLastChildId(tree.lastEntryId() + treeOffset);
438
439 return true;
440 }
441
442 void
stopEvent(uint32_t id,uint64_t timestamp)443 TraceLoggerGraph::stopEvent(uint32_t id, uint64_t timestamp)
444 {
445 #ifdef DEBUG
446 if (id != TraceLogger_Scripts &&
447 id != TraceLogger_Engine &&
448 stack.size() > 1 &&
449 stack.lastEntry().active())
450 {
451 TreeEntry entry;
452 MOZ_ASSERT(getTreeEntry(stack.lastEntry().treeId(), &entry));
453 MOZ_ASSERT(entry.textId() == id);
454 }
455 #endif
456
457 stopEvent(timestamp);
458 }
459
460 void
stopEvent(uint64_t timestamp)461 TraceLoggerGraph::stopEvent(uint64_t timestamp)
462 {
463 if (enabled && stack.lastEntry().active()) {
464 if (!updateStop(stack.lastEntry().treeId(), timestamp)) {
465 fprintf(stderr, "TraceLogging: Failed to stop an event.\n");
466 enabled = false;
467 failed = true;
468 return;
469 }
470 }
471 if (stack.size() == 1) {
472 if (!enabled)
473 return;
474
475 // Forcefully disable logging. We have no stack information anymore.
476 logTimestamp(TraceLogger_Disable, timestamp);
477 return;
478 }
479 stack.pop();
480 }
481
482 void
logTimestamp(uint32_t id,uint64_t timestamp)483 TraceLoggerGraph::logTimestamp(uint32_t id, uint64_t timestamp)
484 {
485 if (failed)
486 return;
487
488 if (id == TraceLogger_Enable)
489 enabled = true;
490
491 if (!enabled)
492 return;
493
494 if (id == TraceLogger_Disable)
495 disable(timestamp);
496
497 MOZ_ASSERT(eventFile);
498
499 // Format data in big endian
500 timestamp = NativeEndian::swapToBigEndian(timestamp);
501 id = NativeEndian::swapToBigEndian(id);
502
503 // The layout of the event log in the log file is:
504 // [timestamp, textId]
505 size_t itemsWritten = 0;
506 itemsWritten += fwrite(×tamp, sizeof(uint64_t), 1, eventFile);
507 itemsWritten += fwrite(&id, sizeof(uint32_t), 1, eventFile);
508 if (itemsWritten < 2) {
509 failed = true;
510 enabled = false;
511 }
512 }
513
514 bool
getTreeEntry(uint32_t treeId,TreeEntry * entry)515 TraceLoggerGraph::getTreeEntry(uint32_t treeId, TreeEntry* entry)
516 {
517 // Entry is still in memory
518 if (treeId >= treeOffset) {
519 *entry = tree[treeId - treeOffset];
520 return true;
521 }
522
523 int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET);
524 if (success != 0)
525 return false;
526
527 size_t itemsRead = fread((void*)entry, sizeof(TreeEntry), 1, treeFile);
528 if (itemsRead < 1)
529 return false;
530
531 entryToSystemEndian(entry);
532 return true;
533 }
534
535 bool
saveTreeEntry(uint32_t treeId,TreeEntry * entry)536 TraceLoggerGraph::saveTreeEntry(uint32_t treeId, TreeEntry* entry)
537 {
538 int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET);
539 if (success != 0)
540 return false;
541
542 entryToBigEndian(entry);
543
544 size_t itemsWritten = fwrite(entry, sizeof(TreeEntry), 1, treeFile);
545 if (itemsWritten < 1)
546 return false;
547
548 return true;
549 }
550
551 bool
updateHasChildren(uint32_t treeId,bool hasChildren)552 TraceLoggerGraph::updateHasChildren(uint32_t treeId, bool hasChildren)
553 {
554 if (treeId < treeOffset) {
555 TreeEntry entry;
556 if (!getTreeEntry(treeId, &entry))
557 return false;
558 entry.setHasChildren(hasChildren);
559 if (!saveTreeEntry(treeId, &entry))
560 return false;
561 return true;
562 }
563
564 tree[treeId - treeOffset].setHasChildren(hasChildren);
565 return true;
566 }
567
568 bool
updateNextId(uint32_t treeId,uint32_t nextId)569 TraceLoggerGraph::updateNextId(uint32_t treeId, uint32_t nextId)
570 {
571 if (treeId < treeOffset) {
572 TreeEntry entry;
573 if (!getTreeEntry(treeId, &entry))
574 return false;
575 entry.setNextId(nextId);
576 if (!saveTreeEntry(treeId, &entry))
577 return false;
578 return true;
579 }
580
581 tree[treeId - treeOffset].setNextId(nextId);
582 return true;
583 }
584
585 bool
updateStop(uint32_t treeId,uint64_t timestamp)586 TraceLoggerGraph::updateStop(uint32_t treeId, uint64_t timestamp)
587 {
588 if (treeId < treeOffset) {
589 TreeEntry entry;
590 if (!getTreeEntry(treeId, &entry))
591 return false;
592 entry.setStop(timestamp);
593 if (!saveTreeEntry(treeId, &entry))
594 return false;
595 return true;
596 }
597
598 tree[treeId - treeOffset].setStop(timestamp);
599 return true;
600 }
601
602 void
disable(uint64_t timestamp)603 TraceLoggerGraph::disable(uint64_t timestamp)
604 {
605 MOZ_ASSERT(enabled);
606 while (stack.size() > 1)
607 stopEvent(timestamp);
608
609 enabled = false;
610 }
611
612 void
log(ContinuousSpace<EventEntry> & events)613 TraceLoggerGraph::log(ContinuousSpace<EventEntry>& events)
614 {
615 for (uint32_t i = 0; i < events.size(); i++) {
616 if (events[i].textId == TraceLogger_Stop)
617 stopEvent(events[i].time);
618 else if (TLTextIdIsTreeEvent(events[i].textId))
619 startEvent(events[i].textId, events[i].time);
620 else
621 logTimestamp(events[i].textId, events[i].time);
622 }
623 }
624
625 void
addTextId(uint32_t id,const char * text)626 TraceLoggerGraph::addTextId(uint32_t id, const char* text)
627 {
628 if (failed)
629 return;
630
631 // Assume ids are given in order. Which is currently true.
632 #ifdef DEBUG
633 MOZ_ASSERT(id == nextTextId);
634 nextTextId++;
635 #endif
636
637 if (id > 0) {
638 int written = fprintf(dictFile, ",\n");
639 if (written < 0) {
640 failed = true;
641 return;
642 }
643 }
644
645 if (!js::FileEscapedString(dictFile, text, strlen(text), '"'))
646 failed = true;
647 }
648
649 #undef getpid
650