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(&timestamp, 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