1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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/TraceLogging.h"
8
9 #include "mozilla/EndianUtils.h"
10 #include "mozilla/MemoryReporting.h"
11
12 #include <algorithm>
13 #include <string.h>
14 #include <utility>
15
16 #include "jit/BaselineJIT.h"
17 #include "jit/JitSpewer.h"
18 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
19 #include "js/Printf.h"
20 #include "js/TraceLoggerAPI.h"
21 #include "threading/LockGuard.h"
22 #include "util/GetPidProvider.h" // getpid()
23 #include "util/Text.h"
24 #include "vm/Activation.h" // js::ActivationIterator
25 #include "vm/FrameIter.h" // js::JitFrameIter
26 #include "vm/JSContext.h"
27 #include "vm/JSScript.h"
28 #include "vm/Runtime.h"
29 #include "vm/Time.h"
30 #include "vm/TraceLoggingGraph.h"
31
32 using namespace js;
33
34 static TraceLoggerThreadState* traceLoggerState = nullptr;
35
getTraceLoggerSupported()36 static bool getTraceLoggerSupported() {
37 char* str = getenv("JS_TRACE_LOGGING");
38
39 if (!str) {
40 // Default to unsupported.
41 return false;
42 }
43
44 if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0 ||
45 strcmp(str, "0") == 0) {
46 return false;
47 }
48
49 if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0 ||
50 strcmp(str, "1") == 0) {
51 fprintf(stderr, "Warning: TraceLogger disabled temporarily, bug 1565788\n");
52 return false;
53 }
54
55 fprintf(stderr, "Warning: I didn't understand JS_TRACE_LOGGING=\"%s\"\n",
56 str);
57 return false;
58 }
59
SizeOfTraceLogState(mozilla::MallocSizeOf mallocSizeOf)60 size_t js::SizeOfTraceLogState(mozilla::MallocSizeOf mallocSizeOf) {
61 return traceLoggerState ? traceLoggerState->sizeOfIncludingThis(mallocSizeOf)
62 : 0;
63 }
64
ResetTraceLogger()65 void js::ResetTraceLogger() {
66 if (!traceLoggerState) {
67 return;
68 }
69
70 traceLoggerState->clear();
71 }
72
DestroyTraceLoggerThreadState()73 void js::DestroyTraceLoggerThreadState() {
74 if (traceLoggerState) {
75 js_delete(traceLoggerState);
76 traceLoggerState = nullptr;
77 }
78 }
79
80 #ifdef DEBUG
AssertCurrentThreadOwnsTraceLoggerThreadStateLock()81 void js::AssertCurrentThreadOwnsTraceLoggerThreadStateLock() {
82 if (traceLoggerState) {
83 traceLoggerState->lock.assertOwnedByCurrentThread();
84 }
85 }
86 #endif
87
DestroyTraceLogger(TraceLoggerThread * logger)88 void js::DestroyTraceLogger(TraceLoggerThread* logger) {
89 MOZ_ASSERT(traceLoggerState);
90 traceLoggerState->destroyLogger(logger);
91 }
92
init()93 bool TraceLoggerThread::init() {
94 if (!events_.init()) {
95 return false;
96 }
97
98 // Minimum amount of capacity needed for operation to allow flushing.
99 // Flushing requires space for the actual event and two spaces to log the
100 // start and stop of flushing.
101 if (!events_.ensureSpaceBeforeAdd(3)) {
102 return false;
103 }
104
105 char* buffer = js_pod_malloc<char>(32);
106 js::ThisThread::GetName(buffer, 32);
107 threadName_.reset(buffer);
108
109 return true;
110 }
111
initGraph()112 void TraceLoggerThread::initGraph() {
113 // Create a graph. I don't like this is called reset, but it locks the
114 // graph into the UniquePtr. So it gets deleted when TraceLoggerThread
115 // is destructed.
116 graph_.reset(js_new<TraceLoggerGraph>());
117 if (!graph_.get()) {
118 return;
119 }
120
121 MOZ_ASSERT(traceLoggerState);
122 bool graphFile = traceLoggerState->isGraphFileEnabled();
123 double delta =
124 traceLoggerState->getTimeStampOffset(mozilla::TimeStamp::Now());
125 uint64_t start = static_cast<uint64_t>(delta);
126 if (!graph_->init(start, graphFile)) {
127 graph_ = nullptr;
128 return;
129 }
130
131 if (graphFile) {
132 // Report the textIds to the graph.
133 for (uint32_t i = 0; i < TraceLogger_TreeItemEnd; i++) {
134 TraceLoggerTextId id = TraceLoggerTextId(i);
135 graph_->addTextId(i, TLTextIdString(id));
136 }
137 graph_->addTextId(TraceLogger_TreeItemEnd, "TraceLogger internal");
138 for (uint32_t i = TraceLogger_TreeItemEnd + 1; i < TraceLogger_Last; i++) {
139 TraceLoggerTextId id = TraceLoggerTextId(i);
140 graph_->addTextId(i, TLTextIdString(id));
141 }
142 }
143 }
144
disableAllTextIds()145 void TraceLoggerThreadState::disableAllTextIds() {
146 for (uint32_t i = 1; i < TraceLogger_Last; i++) {
147 enabledTextIds[i] = false;
148 }
149 }
150
enableTextIdsForProfiler()151 void TraceLoggerThreadState::enableTextIdsForProfiler() {
152 enableDefaultLogging();
153 }
154
disableTextIdsForProfiler()155 void TraceLoggerThreadState::disableTextIdsForProfiler() {
156 disableAllTextIds();
157 // We have to keep the Baseline and IonMonkey id's alive because they control
158 // whether the jitted codegen has tracelogger start & stop events builtin.
159 // Otherwise, we end up in situations when some jitted code that was created
160 // before the profiler was even started ends up not starting and stoping any
161 // events. The TraceLogger_Engine stop events can accidentally stop the wrong
162 // event in this case, and then it's no longer possible to build a graph.
163 enabledTextIds[TraceLogger_Engine] = true;
164 enabledTextIds[TraceLogger_Interpreter] = true;
165 enabledTextIds[TraceLogger_Baseline] = true;
166 enabledTextIds[TraceLogger_IonMonkey] = true;
167 }
168
enableDefaultLogging()169 void TraceLoggerThreadState::enableDefaultLogging() {
170 enabledTextIds[TraceLogger_AnnotateScripts] = true;
171 enabledTextIds[TraceLogger_Bailout] = true;
172 enabledTextIds[TraceLogger_Baseline] = true;
173 enabledTextIds[TraceLogger_BaselineCompilation] = true;
174 enabledTextIds[TraceLogger_GC] = true;
175 enabledTextIds[TraceLogger_GCAllocation] = true;
176 enabledTextIds[TraceLogger_GCSweeping] = true;
177 enabledTextIds[TraceLogger_Interpreter] = true;
178 enabledTextIds[TraceLogger_IonAnalysis] = true;
179 enabledTextIds[TraceLogger_IonCompilation] = true;
180 enabledTextIds[TraceLogger_IonLinking] = true;
181 enabledTextIds[TraceLogger_IonMonkey] = true;
182 enabledTextIds[TraceLogger_MinorGC] = true;
183 enabledTextIds[TraceLogger_Frontend] = true;
184 enabledTextIds[TraceLogger_ParsingFull] = true;
185 enabledTextIds[TraceLogger_ParsingSyntax] = true;
186 enabledTextIds[TraceLogger_BytecodeEmission] = true;
187 enabledTextIds[TraceLogger_IrregexpCompile] = true;
188 enabledTextIds[TraceLogger_IrregexpExecute] = true;
189 enabledTextIds[TraceLogger_Scripts] = true;
190 enabledTextIds[TraceLogger_Engine] = true;
191 enabledTextIds[TraceLogger_WasmCompilation] = true;
192 enabledTextIds[TraceLogger_Invalidation] = true;
193 }
194
enableIonLogging()195 void TraceLoggerThreadState::enableIonLogging() {
196 enabledTextIds[TraceLogger_IonCompilation] = true;
197 enabledTextIds[TraceLogger_IonLinking] = true;
198 enabledTextIds[TraceLogger_PruneUnusedBranches] = true;
199 enabledTextIds[TraceLogger_FoldTests] = true;
200 enabledTextIds[TraceLogger_SplitCriticalEdges] = true;
201 enabledTextIds[TraceLogger_RenumberBlocks] = true;
202 enabledTextIds[TraceLogger_ScalarReplacement] = true;
203 enabledTextIds[TraceLogger_DominatorTree] = true;
204 enabledTextIds[TraceLogger_PhiAnalysis] = true;
205 enabledTextIds[TraceLogger_MakeLoopsContiguous] = true;
206 enabledTextIds[TraceLogger_ApplyTypes] = true;
207 enabledTextIds[TraceLogger_EagerSimdUnbox] = true;
208 enabledTextIds[TraceLogger_AliasAnalysis] = true;
209 enabledTextIds[TraceLogger_GVN] = true;
210 enabledTextIds[TraceLogger_LICM] = true;
211 enabledTextIds[TraceLogger_RangeAnalysis] = true;
212 enabledTextIds[TraceLogger_LoopUnrolling] = true;
213 enabledTextIds[TraceLogger_FoldLinearArithConstants] = true;
214 enabledTextIds[TraceLogger_EffectiveAddressAnalysis] = true;
215 enabledTextIds[TraceLogger_AlignmentMaskAnalysis] = true;
216 enabledTextIds[TraceLogger_EliminateDeadCode] = true;
217 enabledTextIds[TraceLogger_ReorderInstructions] = true;
218 enabledTextIds[TraceLogger_EdgeCaseAnalysis] = true;
219 enabledTextIds[TraceLogger_EliminateRedundantChecks] = true;
220 enabledTextIds[TraceLogger_AddKeepAliveInstructions] = true;
221 enabledTextIds[TraceLogger_GenerateLIR] = true;
222 enabledTextIds[TraceLogger_RegisterAllocation] = true;
223 enabledTextIds[TraceLogger_GenerateCode] = true;
224 enabledTextIds[TraceLogger_Scripts] = true;
225 }
226
enableFrontendLogging()227 void TraceLoggerThreadState::enableFrontendLogging() {
228 enabledTextIds[TraceLogger_Frontend] = true;
229 enabledTextIds[TraceLogger_ParsingFull] = true;
230 enabledTextIds[TraceLogger_ParsingSyntax] = true;
231 enabledTextIds[TraceLogger_BytecodeEmission] = true;
232 enabledTextIds[TraceLogger_BytecodeFoldConstants] = true;
233 enabledTextIds[TraceLogger_BytecodeNameFunctions] = true;
234 }
235
~TraceLoggerThread()236 TraceLoggerThread::~TraceLoggerThread() {
237 if (graph_.get()) {
238 if (!failed_) {
239 graph_->log(events_, traceLoggerState->startTime);
240 }
241 graph_ = nullptr;
242 }
243 }
244
enable()245 bool TraceLoggerThread::enable() {
246 if (enabled_ > 0) {
247 enabled_++;
248 return true;
249 }
250
251 if (failed_) {
252 return false;
253 }
254
255 enabled_ = 1;
256 logTimestamp(TraceLogger_Enable);
257
258 return true;
259 }
260
fail(JSContext * cx,const char * error)261 bool TraceLoggerThread::fail(JSContext* cx, const char* error) {
262 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
263 JSMSG_TRACELOGGER_ENABLE_FAIL, error);
264 failed_ = true;
265 enabled_ = 0;
266
267 return false;
268 }
269
silentFail(const char * error)270 void TraceLoggerThread::silentFail(const char* error) {
271 traceLoggerState->maybeSpewError(error);
272 failed_ = true;
273 enabled_ = 0;
274 }
275
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const276 size_t TraceLoggerThread::sizeOfExcludingThis(
277 mozilla::MallocSizeOf mallocSizeOf) const {
278 size_t size = 0;
279 #ifdef DEBUG
280 size += graphStack_.sizeOfExcludingThis(mallocSizeOf);
281 #endif
282 size += events_.sizeOfExcludingThis(mallocSizeOf);
283 if (graph_.get()) {
284 size += graph_->sizeOfIncludingThis(mallocSizeOf);
285 }
286 return size;
287 }
288
sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const289 size_t TraceLoggerThread::sizeOfIncludingThis(
290 mozilla::MallocSizeOf mallocSizeOf) const {
291 return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
292 }
293
enable(JSContext * cx)294 bool TraceLoggerThread::enable(JSContext* cx) {
295 using namespace js::jit;
296
297 if (!enable()) {
298 return fail(cx, "internal error");
299 }
300
301 if (enabled_ == 1) {
302 // Get the top Activation to log the top script/pc (No inlined frames).
303 ActivationIterator iter(cx);
304 Activation* act = iter.activation();
305
306 if (!act) {
307 return fail(cx, "internal error");
308 }
309
310 JSScript* script = nullptr;
311 int32_t engine = 0;
312
313 if (act->isJit()) {
314 JitFrameIter frame(iter->asJit());
315
316 while (!frame.done()) {
317 if (frame.isWasm()) {
318 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
319 JSMSG_TRACELOGGER_ENABLE_FAIL,
320 "not yet supported in wasm code");
321 return false;
322 }
323 if (frame.asJSJit().isScripted()) {
324 break;
325 }
326 ++frame;
327 }
328
329 MOZ_ASSERT(!frame.done());
330
331 const JSJitFrameIter& jitFrame = frame.asJSJit();
332 MOZ_ASSERT(jitFrame.isIonJS() || jitFrame.isBaselineJS());
333
334 script = jitFrame.script();
335 engine =
336 jitFrame.isIonJS() ? TraceLogger_IonMonkey : TraceLogger_Baseline;
337 } else {
338 MOZ_ASSERT(act->isInterpreter());
339 InterpreterFrame* fp = act->asInterpreter()->current();
340 MOZ_ASSERT(!fp->runningInJit());
341
342 script = fp->script();
343 engine = TraceLogger_Interpreter;
344 }
345 if (script->compartment() != cx->compartment()) {
346 return fail(cx, "compartment mismatch");
347 }
348
349 TraceLoggerEvent event(TraceLogger_Scripts, script);
350 startEvent(event);
351 startEvent(engine);
352 }
353
354 return true;
355 }
356
disable(bool force,const char * error)357 bool TraceLoggerThread::disable(bool force, const char* error) {
358 if (failed_) {
359 MOZ_ASSERT(enabled_ == 0);
360 return false;
361 }
362
363 if (enabled_ == 0) {
364 return true;
365 }
366
367 if (enabled_ > 1 && !force) {
368 enabled_--;
369 return true;
370 }
371
372 if (force) {
373 traceLoggerState->maybeSpewError(error);
374 }
375
376 logTimestamp(TraceLogger_Disable);
377 enabled_ = 0;
378
379 return true;
380 }
381
maybeEventText(uint32_t id)382 const char* TraceLoggerThread::maybeEventText(uint32_t id) {
383 if (TLTextIdIsEnumEvent(id)) {
384 return TLTextIdString(static_cast<TraceLoggerTextId>(id));
385 }
386 return traceLoggerState->maybeEventText(id);
387 }
388
getPayload(uint32_t id)389 TraceLoggerEventPayload* TraceLoggerThreadState::getPayload(uint32_t id) {
390 if (TLTextIdIsEnumEvent(id)) {
391 return nullptr;
392 }
393
394 TextIdToPayloadMap::Ptr p = textIdPayloads.lookup(id);
395 if (!p) {
396 return nullptr;
397 }
398
399 p->value()->use();
400 return p->value();
401 }
402
maybeEventText(uint32_t id)403 const char* TraceLoggerThreadState::maybeEventText(uint32_t id) {
404 LockGuard<Mutex> guard(lock);
405
406 TextIdToPayloadMap::Ptr p = textIdPayloads.lookup(id);
407 if (!p) {
408 return nullptr;
409 }
410
411 uint32_t dictId = p->value()->dictionaryId();
412 MOZ_ASSERT(dictId < nextDictionaryId);
413 return dictionaryData[dictId].get();
414 }
415
maybeEventText(TraceLoggerEventPayload * p)416 const char* TraceLoggerThreadState::maybeEventText(TraceLoggerEventPayload* p) {
417 LockGuard<Mutex> guard(lock);
418 if (!p) {
419 return nullptr;
420 }
421
422 uint32_t dictId = p->dictionaryId();
423 MOZ_ASSERT(dictId < nextDictionaryId);
424 return dictionaryData[dictId].get();
425 }
426
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)427 size_t TraceLoggerThreadState::sizeOfExcludingThis(
428 mozilla::MallocSizeOf mallocSizeOf) {
429 LockGuard<Mutex> guard(lock);
430
431 // Do not count threadLoggers since they are counted by
432 // JSContext::traceLogger.
433
434 size_t size = 0;
435 size += dictionaryData.sizeOfExcludingThis(mallocSizeOf);
436 size += payloadDictionary.shallowSizeOfExcludingThis(mallocSizeOf);
437 size += textIdPayloads.shallowSizeOfExcludingThis(mallocSizeOf);
438 for (TextIdToPayloadMap::Range r = textIdPayloads.all(); !r.empty();
439 r.popFront()) {
440 r.front().value()->sizeOfIncludingThis(mallocSizeOf);
441 }
442
443 return size;
444 }
445
getOrCreateEventPayload(const char * text)446 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
447 const char* text) {
448 LockGuard<Mutex> guard(lock);
449
450 uint32_t dictId = nextDictionaryId;
451
452 StringHashToDictionaryMap::AddPtr dictp =
453 payloadDictionary.lookupForAdd(text);
454 if (dictp) {
455 dictId = dictp->value();
456 MOZ_ASSERT(dictId < nextDictionaryId); // Sanity check.
457 } else {
458 UniqueChars str = DuplicateString(text);
459 if (!str) {
460 return nullptr;
461 }
462 if (!payloadDictionary.add(dictp, str.get(), nextDictionaryId)) {
463 return nullptr;
464 }
465 if (!dictionaryData.append(std::move(str))) {
466 return nullptr;
467 }
468
469 nextDictionaryId++;
470 }
471
472 // Look for a free entry, as some textId's may
473 // already be taken from previous profiling sessions.
474 while (textIdPayloads.has(nextTextId)) {
475 nextTextId++;
476 }
477
478 auto* payload = js_new<TraceLoggerEventPayload>(nextTextId, dictId);
479 if (!payload) {
480 return nullptr;
481 }
482
483 if (!textIdPayloads.putNew(nextTextId, payload)) {
484 js_delete(payload);
485 return nullptr;
486 }
487
488 payload->use();
489
490 nextTextId++;
491
492 return payload;
493 }
494
getOrCreateEventPayload(const char * filename,uint32_t lineno,uint32_t colno)495 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
496 const char* filename, uint32_t lineno, uint32_t colno) {
497 if (!filename) {
498 filename = "<unknown>";
499 }
500
501 TraceLoggerEventPayload* payload = getOrCreateEventPayload(filename);
502 if (!payload) {
503 return nullptr;
504 }
505
506 payload->setLine(lineno);
507 payload->setColumn(colno);
508
509 return payload;
510 }
511
getOrCreateEventPayload(JSScript * script)512 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
513 JSScript* script) {
514 return getOrCreateEventPayload(script->filename(), script->lineno(),
515 script->column());
516 }
517
purgeUnusedPayloads()518 void TraceLoggerThreadState::purgeUnusedPayloads() {
519 // Care needs to be taken to maintain a coherent state in this function,
520 // as payloads can have their use count change at any time from non-zero to
521 // zero (but not the other way around; see TraceLoggerEventPayload::use()).
522 LockGuard<Mutex> guard(lock);
523
524 // Free all other payloads that have no uses anymore.
525 for (TextIdToPayloadMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) {
526 if (e.front().value()->uses() == 0) {
527 uint32_t dictId = e.front().value()->dictionaryId();
528 dictionaryData.erase(dictionaryData.begin() + dictId);
529 js_delete(e.front().value());
530 e.removeFront();
531 }
532 }
533 }
534
startEvent(TraceLoggerTextId id)535 void TraceLoggerThread::startEvent(TraceLoggerTextId id) {
536 startEvent(uint32_t(id));
537 }
538
startEvent(const TraceLoggerEvent & event)539 void TraceLoggerThread::startEvent(const TraceLoggerEvent& event) {
540 if (!event.hasTextId()) {
541 if (!enabled()) {
542 return;
543 }
544 startEvent(TraceLogger_Error);
545 disable(/* force = */ true,
546 "TraceLogger encountered an empty event. "
547 "Potentially due to OOM during creation of "
548 "this event. Disabling TraceLogger.");
549 return;
550 }
551 startEvent(event.textId());
552 }
553
startEvent(uint32_t id)554 void TraceLoggerThread::startEvent(uint32_t id) {
555 if (!jit::JitOptions.enableTraceLogger) {
556 return;
557 }
558
559 MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
560 MOZ_ASSERT(traceLoggerState);
561 if (!traceLoggerState->isTextIdEnabled(id)) {
562 return;
563 }
564
565 #ifdef DEBUG
566 if (enabled_ > 0) {
567 AutoEnterOOMUnsafeRegion oomUnsafe;
568 if (!graphStack_.append(id)) {
569 oomUnsafe.crash("Could not add item to debug stack.");
570 }
571 }
572 #endif
573
574 if (graph_.get() && traceLoggerState->isGraphFileEnabled()) {
575 // Flush each textId to disk. textId values up to TraceLogger_Last are
576 // statically defined and each one has an associated constant event string
577 // defined by TLTextIdString(). For any events with textId >=
578 // TraceLogger_Last the payload associated with that textId must first be
579 // found and then maybeEventText() will find the event string form the
580 // dictionary.
581 for (uint32_t otherId = graph_->nextTextId(); otherId <= id; otherId++) {
582 if (TLTextIdIsEnumEvent(id)) {
583 const char* text = TLTextIdString(static_cast<TraceLoggerTextId>(id));
584 graph_->addTextId(otherId, text);
585 } else {
586 TraceLoggerEventPayload* p = traceLoggerState->getPayload(id);
587 if (p) {
588 const char* filename = traceLoggerState->maybeEventText(p);
589 mozilla::Maybe<uint32_t> line = p->line();
590 mozilla::Maybe<uint32_t> column = p->column();
591 graph_->addTextId(otherId, filename, line, column);
592 p->release();
593 }
594 }
595 }
596 }
597
598 log(id);
599 }
600
stopEvent(TraceLoggerTextId id)601 void TraceLoggerThread::stopEvent(TraceLoggerTextId id) {
602 stopEvent(uint32_t(id));
603 }
604
stopEvent(const TraceLoggerEvent & event)605 void TraceLoggerThread::stopEvent(const TraceLoggerEvent& event) {
606 if (!event.hasTextId()) {
607 stopEvent(TraceLogger_Error);
608 return;
609 }
610 stopEvent(event.textId());
611 }
612
stopEvent(uint32_t id)613 void TraceLoggerThread::stopEvent(uint32_t id) {
614 if (!jit::JitOptions.enableTraceLogger) {
615 return;
616 }
617
618 MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
619 MOZ_ASSERT(traceLoggerState);
620 if (!traceLoggerState->isTextIdEnabled(id)) {
621 return;
622 }
623
624 #ifdef DEBUG
625 if (!graphStack_.empty()) {
626 uint32_t prev = graphStack_.popCopy();
627 if (id == TraceLogger_Error || prev == TraceLogger_Error) {
628 // When encountering an Error id the stack will most likely not be correct
629 // anymore. Ignore this.
630 } else if (id == TraceLogger_Engine) {
631 MOZ_ASSERT(prev == TraceLogger_IonMonkey ||
632 prev == TraceLogger_Baseline ||
633 prev == TraceLogger_Interpreter);
634 } else if (id == TraceLogger_Scripts) {
635 MOZ_ASSERT(TLTextIdIsScriptEvent(prev));
636 } else if (TLTextIdIsScriptEvent(id)) {
637 MOZ_ASSERT(TLTextIdIsScriptEvent(prev));
638 if (prev != id) {
639 // Ignore if the text has been flushed already.
640 MOZ_ASSERT_IF(maybeEventText(prev),
641 strcmp(maybeEventText(id), maybeEventText(prev)) == 0);
642 }
643 } else {
644 MOZ_ASSERT(id == prev);
645 }
646 }
647 #endif
648
649 log(TraceLogger_Stop);
650 }
651
AutoTraceLoggerLockGuard()652 JS::AutoTraceLoggerLockGuard::AutoTraceLoggerLockGuard() {
653 traceLoggerState->lock.lock();
654 }
655
~AutoTraceLoggerLockGuard()656 JS::AutoTraceLoggerLockGuard::~AutoTraceLoggerLockGuard() {
657 traceLoggerState->lock.unlock();
658 }
659
NextChunk(JSContext * cx,size_t * dataIndex,char buffer[],size_t bufferSize)660 size_t JS::TraceLoggerDictionaryImpl::NextChunk(JSContext* cx,
661 size_t* dataIndex,
662 char buffer[],
663 size_t bufferSize) {
664 MOZ_ASSERT(dataIndex != nullptr);
665 if (!traceLoggerState || bufferSize == 0 || !buffer ||
666 !jit::JitOptions.enableTraceLogger) {
667 return 0;
668 }
669
670 size_t bufferIndex = 0;
671
672 const char* eventString = nullptr;
673 if (TLTextIdIsEnumEvent(*dataIndex)) {
674 eventString = TLTextIdString(static_cast<TraceLoggerTextId>(*dataIndex));
675 } else {
676 uint32_t dictId = *dataIndex - TraceLogger_Last;
677 if (dictId < traceLoggerState->dictionaryData.length()) {
678 eventString = traceLoggerState->dictionaryData[dictId].get();
679 MOZ_ASSERT(eventString);
680 }
681 }
682
683 if (eventString) {
684 size_t length = strlen(eventString);
685 if (length < bufferSize - 1) {
686 memcpy(buffer, eventString, length);
687 buffer[length] = '\0';
688 bufferIndex = length;
689 } else {
690 memcpy(buffer, eventString, bufferSize);
691 buffer[bufferSize - 1] = '\0';
692 bufferIndex = bufferSize - 1;
693 }
694 }
695
696 (*dataIndex)++;
697 return bufferIndex;
698 }
699
NextChunk(JSContext * cx,size_t * dataIndex,uint32_t buffer[],size_t bufferSize)700 size_t JS::TraceLoggerIdImpl::NextChunk(JSContext* cx, size_t* dataIndex,
701 uint32_t buffer[], size_t bufferSize) {
702 MOZ_ASSERT(dataIndex != nullptr);
703 if (!cx || !cx->traceLogger) {
704 return 0;
705 }
706
707 if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
708 return 0;
709 }
710
711 size_t bufferIndex = 0;
712 ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
713
714 for (; *dataIndex < events.size(); (*dataIndex)++) {
715 if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
716 continue;
717 }
718
719 if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
720 TraceLoggerEventPayload* payload =
721 traceLoggerState->getPayload(events[*dataIndex].textId);
722 MOZ_ASSERT(payload);
723 if (!payload) {
724 return 0;
725 }
726 // Write the index of this event into the jsTracerDictionary array
727 // property
728 uint32_t dictId = TraceLogger_Last + payload->dictionaryId();
729 buffer[bufferIndex++] = dictId;
730 payload->release();
731 } else {
732 buffer[bufferIndex++] = events[*dataIndex].textId;
733 }
734
735 if (bufferIndex == bufferSize) {
736 break;
737 }
738 }
739
740 return bufferIndex;
741 }
742
NextChunk(JSContext * cx,size_t * dataIndex,int32_t buffer[],size_t bufferSize)743 size_t JS::TraceLoggerLineNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
744 int32_t buffer[],
745 size_t bufferSize) {
746 MOZ_ASSERT(dataIndex != nullptr);
747 if (!cx || !cx->traceLogger) {
748 return 0;
749 }
750
751 if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
752 return 0;
753 }
754
755 size_t bufferIndex = 0;
756 ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
757
758 for (; *dataIndex < events.size(); (*dataIndex)++) {
759 if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
760 continue;
761 }
762
763 if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
764 TraceLoggerEventPayload* payload =
765 traceLoggerState->getPayload(events[*dataIndex].textId);
766 MOZ_ASSERT(payload);
767 if (!payload) {
768 return 0;
769 }
770 mozilla::Maybe<uint32_t> line = payload->line();
771 payload->release();
772 if (line) {
773 buffer[bufferIndex++] = *line;
774 } else {
775 buffer[bufferIndex++] = -1;
776 }
777 } else {
778 buffer[bufferIndex++] = -1;
779 }
780 if (bufferIndex == bufferSize) {
781 break;
782 }
783 }
784
785 return bufferIndex;
786 }
787
NextChunk(JSContext * cx,size_t * dataIndex,int32_t buffer[],size_t bufferSize)788 size_t JS::TraceLoggerColNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
789 int32_t buffer[],
790 size_t bufferSize) {
791 MOZ_ASSERT(dataIndex != nullptr);
792 if (!cx || !cx->traceLogger) {
793 return 0;
794 }
795
796 if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
797 return 0;
798 }
799
800 size_t bufferIndex = 0;
801 ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
802
803 for (; *dataIndex < events.size(); (*dataIndex)++) {
804 if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
805 continue;
806 }
807
808 if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
809 TraceLoggerEventPayload* payload =
810 traceLoggerState->getPayload(events[*dataIndex].textId);
811 MOZ_ASSERT(payload);
812 if (!payload) {
813 return 0;
814 }
815 mozilla::Maybe<uint32_t> column = payload->column();
816 payload->release();
817 if (column) {
818 buffer[bufferIndex++] = *column;
819 } else {
820 buffer[bufferIndex++] = -1;
821 }
822 } else {
823 buffer[bufferIndex++] = -1;
824 }
825 if (bufferIndex == bufferSize) {
826 break;
827 }
828 }
829
830 return bufferIndex;
831 }
832
NextChunk(JSContext * cx,size_t * dataIndex,mozilla::TimeStamp buffer[],size_t bufferSize)833 size_t JS::TraceLoggerTimeStampImpl::NextChunk(JSContext* cx, size_t* dataIndex,
834 mozilla::TimeStamp buffer[],
835 size_t bufferSize) {
836 MOZ_ASSERT(dataIndex != nullptr);
837 if (!cx || !cx->traceLogger) {
838 return 0;
839 }
840
841 if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
842 return 0;
843 }
844
845 size_t bufferIndex = 0;
846 ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
847
848 for (; *dataIndex < events.size(); (*dataIndex)++) {
849 if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
850 continue;
851 }
852 buffer[bufferIndex++] = events[*dataIndex].time;
853 if (bufferIndex == bufferSize) {
854 break;
855 }
856 }
857
858 return bufferIndex;
859 }
860
NextChunk(JSContext * cx,size_t * dataIndex,double buffer[],size_t bufferSize)861 size_t JS::TraceLoggerDurationImpl::NextChunk(JSContext* cx, size_t* dataIndex,
862 double buffer[],
863 size_t bufferSize) {
864 MOZ_ASSERT(dataIndex != nullptr);
865 if (!cx || !cx->traceLogger) {
866 return 0;
867 }
868
869 if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
870 return 0;
871 }
872
873 ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
874 Vector<size_t, 0, js::SystemAllocPolicy> eventStack;
875 using EventDurationMap =
876 HashMap<size_t, double, DefaultHasher<size_t>, SystemAllocPolicy>;
877 EventDurationMap eventMap;
878
879 size_t bufferIndex = 0;
880 for (; *dataIndex < events.size(); (*dataIndex)++) {
881 if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
882 continue;
883 }
884 double duration = 0;
885 if (TLTextIdIsLogEvent(events[*dataIndex].textId)) {
886 // log events are snapshot events with no start & stop
887 duration = -1;
888 } else if (EventDurationMap::Ptr p = eventMap.lookup(*dataIndex)) {
889 // value has already been cached
890 duration = p->value();
891 } else {
892 MOZ_ASSERT(eventStack.empty());
893 if (!eventStack.append(*dataIndex)) {
894 return 0;
895 }
896
897 // Search through the events array to find the matching stop event in
898 // order to calculate the duration time. Cache all other durations we
899 // calculate in the meantime.
900 for (size_t j = *dataIndex + 1; j < events.size(); j++) {
901 uint32_t id = events[j].textId;
902 if (id == TraceLogger_Stop) {
903 uint32_t prev = eventStack.popCopy();
904 double delta = (events[j].time - events[prev].time).ToMicroseconds();
905 if (prev == *dataIndex) {
906 MOZ_ASSERT(eventStack.empty());
907 duration = delta;
908 break;
909 }
910
911 if (!eventMap.putNew(prev, delta)) {
912 return 0;
913 }
914
915 } else if (TLTextIdIsTreeEvent(id)) {
916 if (!eventStack.append(j)) {
917 return 0;
918 }
919 }
920
921 // If we reach the end of the list, use the last event as the end
922 // event for all events remaining on the stack.
923 if (j == events.size() - 1) {
924 while (!eventStack.empty()) {
925 uint32_t prev = eventStack.popCopy();
926 double delta =
927 (events[j].time - events[prev].time).ToMicroseconds();
928 if (prev == *dataIndex) {
929 MOZ_ASSERT(eventStack.empty());
930 duration = delta;
931 } else {
932 if (!eventMap.putNew(prev, delta)) {
933 return 0;
934 }
935 }
936 }
937 }
938 }
939 }
940
941 buffer[bufferIndex++] = duration;
942 if (bufferIndex == bufferSize) {
943 break;
944 }
945 }
946
947 return bufferIndex;
948 }
949
logTimestamp(TraceLoggerTextId id)950 void TraceLoggerThread::logTimestamp(TraceLoggerTextId id) {
951 logTimestamp(uint32_t(id));
952 }
953
logTimestamp(uint32_t id)954 void TraceLoggerThread::logTimestamp(uint32_t id) {
955 MOZ_ASSERT(id > TraceLogger_TreeItemEnd && id < TraceLogger_Last);
956 log(id);
957 }
958
log(uint32_t id)959 void TraceLoggerThread::log(uint32_t id) {
960 if (enabled_ == 0) {
961 return;
962 }
963
964 #ifdef DEBUG
965 if (id == TraceLogger_Disable) {
966 graphStack_.clear();
967 }
968 #endif
969
970 MOZ_ASSERT(traceLoggerState);
971
972 // We request for 3 items to add, since if we don't have enough room
973 // we record the time it took to make more space. To log this information
974 // we need 2 extra free entries.
975 if (!events_.hasSpaceForAdd(3)) {
976 mozilla::TimeStamp start = mozilla::TimeStamp::Now();
977
978 if (!events_.ensureSpaceBeforeAdd(3)) {
979 if (graph_.get()) {
980 graph_->log(events_, traceLoggerState->startTime);
981 }
982
983 // The data structures are full, and the graph file is not enabled
984 // so we cannot flush to disk. Trace logging should stop here.
985 if (!traceLoggerState->isGraphFileEnabled()) {
986 enabled_ = 0;
987 return;
988 }
989
990 iteration_++;
991 events_.clear();
992
993 // Periodically remove unused payloads from the global logger state.
994 traceLoggerState->purgeUnusedPayloads();
995 }
996
997 // Log the time it took to flush the events_ as being from the
998 // Tracelogger.
999 if (graph_.get()) {
1000 MOZ_ASSERT(events_.hasSpaceForAdd(2));
1001 EventEntry& entryStart = events_.pushUninitialized();
1002 entryStart.time = start;
1003 entryStart.textId = TraceLogger_Internal;
1004
1005 EventEntry& entryStop = events_.pushUninitialized();
1006 entryStop.time = mozilla::TimeStamp::Now();
1007 entryStop.textId = TraceLogger_Stop;
1008 }
1009 }
1010
1011 mozilla::TimeStamp time = mozilla::TimeStamp::Now();
1012
1013 EventEntry& entry = events_.pushUninitialized();
1014 entry.time = time;
1015 entry.textId = id;
1016 }
1017
remapDictionaryEntries(mozilla::Vector<UniqueChars,0,SystemAllocPolicy> * newDictionary,uint32_t * newNextDictionaryId)1018 bool TraceLoggerThreadState::remapDictionaryEntries(
1019 mozilla::Vector<UniqueChars, 0, SystemAllocPolicy>* newDictionary,
1020 uint32_t* newNextDictionaryId) {
1021 MOZ_ASSERT(newNextDictionaryId != nullptr && newDictionary != nullptr);
1022
1023 typedef HashMap<uint32_t, uint32_t, DefaultHasher<uint32_t>,
1024 SystemAllocPolicy>
1025 DictionaryMap;
1026 DictionaryMap dictionaryMap;
1027
1028 // Clear all payloads that are not currently used. There may be some events
1029 // that still hold a pointer to a payload. Restarting the profiler may reuse
1030 // the exact same event as a previous session if it's still alive so we need
1031 // to maintain it's existence.
1032 for (TextIdToPayloadMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) {
1033 if (e.front().value()->uses() == 0) {
1034 js_delete(e.front().value());
1035 e.removeFront();
1036 } else {
1037 TraceLoggerEventPayload* payload = e.front().value();
1038 uint32_t dictId = payload->dictionaryId();
1039
1040 if (dictionaryMap.has(dictId)) {
1041 DictionaryMap::Ptr mapPointer = dictionaryMap.lookup(dictId);
1042 MOZ_ASSERT(mapPointer);
1043 payload->setDictionaryId(mapPointer->value());
1044 } else {
1045 if (!newDictionary->append(std::move(dictionaryData[dictId]))) {
1046 return false;
1047 }
1048 payload->setDictionaryId(*newNextDictionaryId);
1049
1050 if (!dictionaryMap.putNew(dictId, *newNextDictionaryId)) {
1051 return false;
1052 }
1053
1054 (*newNextDictionaryId)++;
1055 }
1056 }
1057 }
1058
1059 return true;
1060 }
1061
clear()1062 void TraceLoggerThreadState::clear() {
1063 LockGuard<Mutex> guard(lock);
1064
1065 uint32_t newNextDictionaryId = 0;
1066 mozilla::Vector<UniqueChars, 0, SystemAllocPolicy> newDictionary;
1067 if (remapDictionaryEntries(&newDictionary, &newNextDictionaryId)) {
1068 // Clear and free any data used for the string dictionary.
1069 for (auto range = dictionaryData.all(); !range.empty(); range.popFront()) {
1070 range.front().reset();
1071 }
1072 dictionaryData.clearAndFree();
1073 dictionaryData = std::move(newDictionary);
1074
1075 payloadDictionary.clearAndCompact();
1076
1077 nextTextId = TraceLogger_Last;
1078 nextDictionaryId = newNextDictionaryId;
1079 }
1080
1081 for (TraceLoggerThread* logger : threadLoggers) {
1082 logger->clear();
1083 }
1084 }
1085
clear()1086 void TraceLoggerThread::clear() {
1087 if (graph_.get()) {
1088 graph_.reset();
1089 }
1090
1091 graph_ = nullptr;
1092
1093 #ifdef DEBUG
1094 graphStack_.clear();
1095 #endif
1096
1097 if (!events_.reset()) {
1098 silentFail("Cannot reset event buffer.");
1099 }
1100 }
1101
~TraceLoggerThreadState()1102 TraceLoggerThreadState::~TraceLoggerThreadState() {
1103 while (TraceLoggerThread* logger = threadLoggers.popFirst()) {
1104 js_delete(logger);
1105 }
1106
1107 threadLoggers.clear();
1108
1109 for (TextIdToPayloadMap::Range r = textIdPayloads.all(); !r.empty();
1110 r.popFront()) {
1111 js_delete(r.front().value());
1112 }
1113
1114 #ifdef DEBUG
1115 initialized = false;
1116 #endif
1117 }
1118
init()1119 bool TraceLoggerThreadState::init() {
1120 const char* env = getenv("TLLOG");
1121 if (env) {
1122 if (strstr(env, "help")) {
1123 fflush(nullptr);
1124 printf(
1125 "\n"
1126 "usage: TLLOG=option,option,option,... where options can be:\n"
1127 "\n"
1128 "Collections:\n"
1129 " Default Output all default. It includes:\n"
1130 " AnnotateScripts, Bailout, Baseline, "
1131 "BaselineCompilation, GC,\n"
1132 " GCAllocation, GCSweeping, Interpreter, "
1133 "IonAnalysis, IonCompilation,\n"
1134 " IonLinking, IonMonkey, MinorGC, Frontend, "
1135 "ParsingFull,\n"
1136 " ParsingSyntax, BytecodeEmission, IrregexpCompile, "
1137 "IrregexpExecute,\n"
1138 " Scripts, Engine, WasmCompilation\n"
1139 "\n"
1140 " IonCompiler Output all information about compilation. It "
1141 "includes:\n"
1142 " IonCompilation, IonLinking, PruneUnusedBranches, "
1143 "FoldTests,\n"
1144 " SplitCriticalEdges, RenumberBlocks, "
1145 "ScalarReplacement,\n"
1146 " DominatorTree, PhiAnalysis, MakeLoopsContiguous, "
1147 "ApplyTypes,\n"
1148 " EagerSimdUnbox, AliasAnalysis, GVN, LICM, "
1149 "RangeAnalysis,\n"
1150 " LoopUnrolling, FoldLinearArithConstants, "
1151 "EffectiveAddressAnalysis,\n"
1152 " AlignmentMaskAnalysis, EliminateDeadCode, "
1153 "ReorderInstructions,\n"
1154 " EdgeCaseAnalysis, EliminateRedundantChecks,\n"
1155 " AddKeepAliveInstructions, GenerateLIR, "
1156 "RegisterAllocation,\n"
1157 " GenerateCode, Scripts\n"
1158 "\n"
1159 " VMSpecific Output the specific name of the VM call\n"
1160 "\n"
1161 " Frontend Output all information about frontend compilation. "
1162 "It includes:\n"
1163 " Frontend, ParsingFull, ParsingSyntax, Tokenizing,\n"
1164 " BytecodeEmission, BytecodeFoldConstants, "
1165 "BytecodeNameFunctions\n"
1166 "Specific log items:\n");
1167 for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1168 TraceLoggerTextId id = TraceLoggerTextId(i);
1169 if (!TLTextIdIsTogglable(id)) {
1170 continue;
1171 }
1172 printf(" %s\n", TLTextIdString(id));
1173 }
1174 printf("\n");
1175 exit(0);
1176 /*NOTREACHED*/
1177 }
1178
1179 for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1180 TraceLoggerTextId id = TraceLoggerTextId(i);
1181 if (TLTextIdIsTogglable(id)) {
1182 enabledTextIds[i] = ContainsFlag(env, TLTextIdString(id));
1183 } else {
1184 enabledTextIds[i] = true;
1185 }
1186 }
1187
1188 if (ContainsFlag(env, "Default")) {
1189 enableDefaultLogging();
1190 }
1191
1192 if (ContainsFlag(env, "IonCompiler")) {
1193 enableIonLogging();
1194 }
1195
1196 if (ContainsFlag(env, "Frontend")) {
1197 enableFrontendLogging();
1198 }
1199
1200 #ifdef DEBUG
1201 enabledTextIds[TraceLogger_Error] = true;
1202 #endif
1203
1204 } else {
1205 // Most of the textId's will be enabled through JS::StartTraceLogger when
1206 // the gecko profiler is started.
1207 disableTextIdsForProfiler();
1208 }
1209
1210 enabledTextIds[TraceLogger_Interpreter] = enabledTextIds[TraceLogger_Engine];
1211 enabledTextIds[TraceLogger_Baseline] = enabledTextIds[TraceLogger_Engine];
1212 enabledTextIds[TraceLogger_IonMonkey] = enabledTextIds[TraceLogger_Engine];
1213
1214 enabledTextIds[TraceLogger_Error] = true;
1215
1216 const char* options = getenv("TLOPTIONS");
1217 if (options) {
1218 if (strstr(options, "help")) {
1219 fflush(nullptr);
1220 printf(
1221 "\n"
1222 "usage: TLOPTIONS=option,option,option,... where options can be:\n"
1223 "\n"
1224 " EnableMainThread Start logging main threads immediately.\n"
1225 " EnableOffThread Start logging helper threads "
1226 "immediately.\n"
1227 " EnableGraph Enable the tracelogging graph.\n"
1228 " EnableGraphFile Enable flushing tracelogger data to a "
1229 "file.\n"
1230 " Errors Report errors during tracing to "
1231 "stderr.\n");
1232 printf("\n");
1233 exit(0);
1234 /*NOTREACHED*/
1235 }
1236
1237 if (strstr(options, "EnableMainThread")) {
1238 mainThreadEnabled = true;
1239 }
1240 if (strstr(options, "EnableOffThread")) {
1241 helperThreadEnabled = true;
1242 }
1243 if (strstr(options, "EnableGraph")) {
1244 graphEnabled = true;
1245 }
1246 if (strstr(options, "EnableGraphFile")) {
1247 graphFileEnabled = true;
1248 jit::JitOptions.enableTraceLogger = true;
1249 }
1250 if (strstr(options, "Errors")) {
1251 spewErrors = true;
1252 }
1253 } else {
1254 mainThreadEnabled = true;
1255 helperThreadEnabled = true;
1256 graphEnabled = false;
1257 graphFileEnabled = false;
1258 spewErrors = false;
1259 }
1260
1261 startTime = mozilla::TimeStamp::Now();
1262
1263 #ifdef DEBUG
1264 initialized = true;
1265 #endif
1266
1267 return true;
1268 }
1269
enableTextId(JSContext * cx,uint32_t textId)1270 void TraceLoggerThreadState::enableTextId(JSContext* cx, uint32_t textId) {
1271 MOZ_ASSERT(TLTextIdIsTogglable(textId));
1272
1273 if (enabledTextIds[textId]) {
1274 return;
1275 }
1276
1277 ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
1278
1279 enabledTextIds[textId] = true;
1280 if (textId == TraceLogger_Engine) {
1281 enabledTextIds[TraceLogger_IonMonkey] = true;
1282 enabledTextIds[TraceLogger_Baseline] = true;
1283 enabledTextIds[TraceLogger_Interpreter] = true;
1284 }
1285
1286 if (textId == TraceLogger_Scripts) {
1287 jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), true);
1288 }
1289 if (textId == TraceLogger_Engine) {
1290 jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), true);
1291 }
1292 }
disableTextId(JSContext * cx,uint32_t textId)1293 void TraceLoggerThreadState::disableTextId(JSContext* cx, uint32_t textId) {
1294 MOZ_ASSERT(TLTextIdIsTogglable(textId));
1295
1296 if (!enabledTextIds[textId]) {
1297 return;
1298 }
1299
1300 ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
1301
1302 enabledTextIds[textId] = false;
1303 if (textId == TraceLogger_Engine) {
1304 enabledTextIds[TraceLogger_IonMonkey] = false;
1305 enabledTextIds[TraceLogger_Baseline] = false;
1306 enabledTextIds[TraceLogger_Interpreter] = false;
1307 }
1308
1309 if (textId == TraceLogger_Scripts) {
1310 jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), false);
1311 }
1312 if (textId == TraceLogger_Engine) {
1313 jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), false);
1314 }
1315 }
1316
TraceLoggerForCurrentThread(JSContext * maybecx)1317 TraceLoggerThread* js::TraceLoggerForCurrentThread(JSContext* maybecx) {
1318 if (!traceLoggerState) {
1319 return nullptr;
1320 }
1321 return traceLoggerState->forCurrentThread(maybecx);
1322 }
1323
forCurrentThread(JSContext * maybecx)1324 TraceLoggerThread* TraceLoggerThreadState::forCurrentThread(
1325 JSContext* maybecx) {
1326 if (!jit::JitOptions.enableTraceLogger) {
1327 return nullptr;
1328 }
1329
1330 MOZ_ASSERT(initialized);
1331 MOZ_ASSERT_IF(maybecx, maybecx == TlsContext.get());
1332
1333 JSContext* cx = maybecx ? maybecx : TlsContext.get();
1334 if (!cx) {
1335 return nullptr;
1336 }
1337
1338 if (!cx->traceLogger) {
1339 LockGuard<Mutex> guard(lock);
1340
1341 TraceLoggerThread* logger = js_new<TraceLoggerThread>(cx);
1342 if (!logger) {
1343 return nullptr;
1344 }
1345
1346 if (!logger->init()) {
1347 return nullptr;
1348 }
1349
1350 threadLoggers.insertFront(logger);
1351 cx->traceLogger = logger;
1352
1353 if (graphEnabled) {
1354 logger->initGraph();
1355 }
1356
1357 if (cx->isHelperThreadContext() ? helperThreadEnabled : mainThreadEnabled) {
1358 logger->enable();
1359 }
1360 }
1361
1362 return cx->traceLogger;
1363 }
1364
destroyLogger(TraceLoggerThread * logger)1365 void TraceLoggerThreadState::destroyLogger(TraceLoggerThread* logger) {
1366 MOZ_ASSERT(initialized);
1367 MOZ_ASSERT(logger);
1368 LockGuard<Mutex> guard(lock);
1369
1370 logger->remove();
1371 js_delete(logger);
1372 }
1373
TraceLogTextIdEnabled(uint32_t textId)1374 bool js::TraceLogTextIdEnabled(uint32_t textId) {
1375 if (!traceLoggerState) {
1376 return false;
1377 }
1378 return traceLoggerState->isTextIdEnabled(textId);
1379 }
1380
TraceLogEnableTextId(JSContext * cx,uint32_t textId)1381 void js::TraceLogEnableTextId(JSContext* cx, uint32_t textId) {
1382 if (!traceLoggerState) {
1383 return;
1384 }
1385 traceLoggerState->enableTextId(cx, textId);
1386 }
TraceLogDisableTextId(JSContext * cx,uint32_t textId)1387 void js::TraceLogDisableTextId(JSContext* cx, uint32_t textId) {
1388 if (!traceLoggerState) {
1389 return;
1390 }
1391 traceLoggerState->disableTextId(cx, textId);
1392 }
1393
TraceLoggerEvent(TraceLoggerTextId type,JSScript * script)1394 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, JSScript* script)
1395 : TraceLoggerEvent(type, script->filename(), script->lineno(),
1396 script->column()) {}
1397
TraceLoggerEvent(TraceLoggerTextId type,const char * filename,uint32_t line,uint32_t column)1398 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, const char* filename,
1399 uint32_t line, uint32_t column)
1400 : payload_() {
1401 MOZ_ASSERT(
1402 type == TraceLogger_Scripts || type == TraceLogger_AnnotateScripts ||
1403 type == TraceLogger_InlinedScripts || type == TraceLogger_Frontend);
1404
1405 if (!traceLoggerState || !jit::JitOptions.enableTraceLogger) {
1406 return;
1407 }
1408
1409 // Only log scripts when enabled, otherwise use the more generic type
1410 // (which will get filtered out).
1411 if (!traceLoggerState->isTextIdEnabled(type)) {
1412 payload_.setTextId(type);
1413 return;
1414 }
1415
1416 payload_.setEventPayload(
1417 traceLoggerState->getOrCreateEventPayload(filename, line, column));
1418 }
1419
TraceLoggerEvent(const char * text)1420 TraceLoggerEvent::TraceLoggerEvent(const char* text) : payload_() {
1421 if (jit::JitOptions.enableTraceLogger && traceLoggerState) {
1422 payload_.setEventPayload(traceLoggerState->getOrCreateEventPayload(text));
1423 }
1424 }
1425
~TraceLoggerEvent()1426 TraceLoggerEvent::~TraceLoggerEvent() {
1427 if (hasExtPayload()) {
1428 extPayload()->release();
1429 }
1430 }
1431
textId() const1432 uint32_t TraceLoggerEvent::textId() const {
1433 MOZ_ASSERT(hasTextId());
1434 if (hasExtPayload()) {
1435 return extPayload()->textId();
1436 }
1437 return payload_.textId();
1438 }
1439
operator =(const TraceLoggerEvent & other)1440 TraceLoggerEvent& TraceLoggerEvent::operator=(const TraceLoggerEvent& other) {
1441 if (other.hasExtPayload()) {
1442 other.extPayload()->use();
1443 }
1444 if (hasExtPayload()) {
1445 extPayload()->release();
1446 }
1447
1448 payload_ = other.payload_;
1449
1450 return *this;
1451 }
1452
TraceLoggerEvent(const TraceLoggerEvent & other)1453 TraceLoggerEvent::TraceLoggerEvent(const TraceLoggerEvent& other)
1454 : payload_(other.payload_) {
1455 if (hasExtPayload()) {
1456 extPayload()->use();
1457 }
1458 }
1459
InitTraceLogger()1460 JS_PUBLIC_API bool JS::InitTraceLogger() {
1461 MOZ_RELEASE_ASSERT(!traceLoggerState);
1462
1463 if (!getTraceLoggerSupported()) {
1464 return true;
1465 }
1466
1467 traceLoggerState = js_new<TraceLoggerThreadState>();
1468 if (!traceLoggerState) {
1469 return false;
1470 }
1471
1472 if (!traceLoggerState->init()) {
1473 DestroyTraceLoggerThreadState();
1474 return false;
1475 }
1476
1477 return true;
1478 }
1479
TraceLoggerSupported()1480 JS_PUBLIC_API bool JS::TraceLoggerSupported() { return traceLoggerState; }
1481
1482 // Perform a process wide synchronous spew of every thread that tracelogger has
1483 // captured.
spewTraceLoggerStats()1484 void TraceLoggerThreadState::spewTraceLoggerStats() {
1485 for (TraceLoggerThread* logger : threadLoggers) {
1486 logger->spewTraceLoggerStats();
1487 }
1488 }
1489
1490 // Usage here is JS_TRACELOGGER_SPEW=<event1>,<event2>,etc for custom spewing.
1491 // If the environment variable is not found, we use a default set of events.
getSpewIds(EventVector & spewIds)1492 static bool getSpewIds(EventVector& spewIds) {
1493 const char* env = getenv("JS_TRACELOGGER_SPEW");
1494 if (env) {
1495 for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1496 TraceLoggerTextId id = TraceLoggerTextId(i);
1497 if (ContainsFlag(env, TLTextIdString(id))) {
1498 if (!spewIds.append(id)) {
1499 return false;
1500 }
1501 }
1502 }
1503 } else {
1504 const uint32_t defaultSpewEvents[] = {
1505 TraceLogger_ParsingFull, TraceLogger_Interpreter,
1506 TraceLogger_Baseline, TraceLogger_BaselineCompilation,
1507 TraceLogger_IonMonkey, TraceLogger_IonCompilation,
1508 TraceLogger_Bailout};
1509
1510 for (uint32_t id : defaultSpewEvents) {
1511 if (!spewIds.append(id)) {
1512 return false;
1513 }
1514 }
1515 }
1516
1517 return true;
1518 }
1519
spewHeaderRow(UniqueChars & threadName,EventVector & spewIds)1520 static void spewHeaderRow(UniqueChars& threadName, EventVector& spewIds) {
1521 if (threadName) {
1522 JitSpew(jit::JitSpew_ScriptStats, "Thread: %s (pid=%d)", threadName.get(),
1523 getpid());
1524 } else {
1525 JitSpew(jit::JitSpew_ScriptStats, "Unknown Thread (pid=%d)", getpid());
1526 }
1527
1528 UniqueChars header = JS_smprintf("%10s ", "totalTime");
1529 for (uint32_t i : spewIds) {
1530 TraceLoggerTextId id = TraceLoggerTextId(i);
1531 if (TLTextIdIsLogEvent(id)) {
1532 header =
1533 JS_sprintf_append(std::move(header), "%12s ", TLTextIdString(id));
1534 } else {
1535 header =
1536 JS_sprintf_append(std::move(header), "%25s ", TLTextIdString(id));
1537 }
1538 }
1539
1540 JitSpew(jit::JitSpew_ScriptStats, "%s Script", header.get());
1541 }
1542
spewTraceLoggerStats()1543 void TraceLoggerThread::spewTraceLoggerStats() {
1544 if (!jit::JitOptions.enableTraceLogger) {
1545 return;
1546 }
1547
1548 ScriptMap map;
1549 if (!collectTraceLoggerStats(map)) {
1550 return;
1551 }
1552 if (map.empty()) {
1553 return;
1554 }
1555
1556 SortedStatsVector sorted_map;
1557 if (!sortTraceLoggerStats(map, sorted_map)) {
1558 return;
1559 }
1560 map.clearAndCompact();
1561
1562 EventVector spewIds;
1563 if (!getSpewIds(spewIds)) {
1564 return;
1565 }
1566
1567 // Dynamically generate the header row in JitSpew.
1568 spewHeaderRow(threadName_, spewIds);
1569
1570 for (UniquePtr<ScriptStats>& datap : sorted_map) {
1571 auto& tlevents = datap->events_;
1572 uint32_t selfTime = datap->selfTime;
1573
1574 if (selfTime == 0) {
1575 continue;
1576 }
1577
1578 UniqueChars row = JS_smprintf("%10u ", selfTime);
1579 for (uint32_t i : spewIds) {
1580 TraceLoggerTextId id = TraceLoggerTextId(i);
1581 uint32_t time = tlevents[id].time;
1582 uint32_t freq = tlevents[id].count;
1583 uint32_t percent = (time * 100) / selfTime;
1584 if (TLTextIdIsLogEvent(id)) {
1585 row = JS_sprintf_append(std::move(row), "%12u ", freq);
1586 } else {
1587 row = JS_sprintf_append(std::move(row), "%8u (%3u%%,f=%-7u) ", time,
1588 percent, freq);
1589 }
1590 }
1591 JitSpew(jit::JitSpew_ScriptStats, "%s %s", row.get(), datap->scriptName);
1592
1593 // If structured spewer is enabled, we might as well spew everything.
1594 AutoStructuredSpewer spew(cx_, SpewChannel::ScriptStats, nullptr);
1595 if (spew) {
1596 spew->property("script", datap->scriptName);
1597 spew->property("totalTime", selfTime);
1598 spew->beginListProperty("events");
1599 for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1600 TraceLoggerTextId id = TraceLoggerTextId(i);
1601 if (TLTextIdIsInternalEvent(id) || tlevents[id].count == 0) {
1602 continue;
1603 }
1604
1605 spew->beginObject();
1606 spew->property("id", TLTextIdString(id));
1607 if (TLTextIdIsTreeEvent(id)) {
1608 spew->property("time", tlevents[id].time);
1609 spew->property("frequency", tlevents[id].count);
1610 } else if (TLTextIdIsLogEvent(id)) {
1611 spew->property("frequency", tlevents[id].count);
1612 }
1613 spew->endObject();
1614 }
1615 spew->endList();
1616 }
1617 }
1618 }
1619
updateScriptMap(ScriptMap & map,char * key,uint32_t eventId,uint32_t value)1620 static bool updateScriptMap(ScriptMap& map, char* key, uint32_t eventId,
1621 uint32_t value) {
1622 if (!key) {
1623 return false;
1624 }
1625
1626 if (!map.has(key)) {
1627 UniquePtr<ScriptStats> datap;
1628 datap.reset(js_new<ScriptStats>(key));
1629 if (!map.putNew(key, std::move(datap))) {
1630 return false;
1631 }
1632 }
1633 ScriptMap::Ptr p = map.lookup(key);
1634 p->value()->events_[eventId].time += value;
1635 p->value()->events_[eventId].count++;
1636
1637 if (TLTextIdIsTreeEvent(eventId)) {
1638 p->value()->selfTime += value;
1639 }
1640 return true;
1641 }
1642
getFullScriptName(uint32_t textId)1643 UniqueChars TraceLoggerThreadState::getFullScriptName(uint32_t textId) {
1644 TraceLoggerEventPayload* payload = getPayload(textId);
1645 MOZ_ASSERT(payload);
1646 if (!payload) {
1647 return nullptr;
1648 }
1649 char* filename = dictionaryData[payload->dictionaryId()].get();
1650 uint32_t lineno = payload->line() ? *(payload->line()) : 0;
1651 uint32_t colno = payload->column() ? *(payload->column()) : 0;
1652 UniqueChars scriptName = JS_smprintf("%s:%u:%u", filename, lineno, colno);
1653 payload->release();
1654 return scriptName;
1655 }
1656
sortBySelfTime(const UniquePtr<ScriptStats> & lhs,const UniquePtr<ScriptStats> & rhs)1657 static bool sortBySelfTime(const UniquePtr<ScriptStats>& lhs,
1658 const UniquePtr<ScriptStats>& rhs) {
1659 return lhs.get()->selfTime > rhs.get()->selfTime;
1660 }
1661
sortTraceLoggerStats(ScriptMap & map,SortedStatsVector & sorted_map)1662 bool TraceLoggerThread::sortTraceLoggerStats(ScriptMap& map,
1663 SortedStatsVector& sorted_map) {
1664 for (auto range = map.all(); !range.empty(); range.popFront()) {
1665 if (!sorted_map.append(std::move(range.front().value()))) {
1666 return false;
1667 }
1668 }
1669
1670 std::sort(sorted_map.begin(), sorted_map.end(), sortBySelfTime);
1671
1672 return true;
1673 }
1674
1675 // Traverse each event and calculate the self-time, along with the script that
1676 // each event belongs to. We do this quickly by maintaining two stacks:
1677 // (i) eventStack - Each new event encountered is pushed onto the stack. Events
1678 // are popped off whenever a TraceLogger_Stop is encountered
1679 // and sent to updateScriptMap.
1680 // (ii) funcStack - Each new script encountered is pushed onto this stack.
1681 // Elements are popped off whenever a TraceLogger_Stop is
1682 // encountered that matches a script event on the top of
1683 // eventStack.
collectTraceLoggerStats(ScriptMap & map)1684 bool TraceLoggerThread::collectTraceLoggerStats(ScriptMap& map) {
1685 struct eventInfo {
1686 uint32_t textId;
1687 uint32_t time;
1688 mozilla::TimeStamp start;
1689
1690 explicit eventInfo(uint32_t textId_) : textId(textId_), time(0) {}
1691 };
1692
1693 Vector<eventInfo*, 0, js::SystemAllocPolicy> eventStack;
1694 Vector<uint32_t, 0, js::SystemAllocPolicy> funcStack;
1695
1696 mozilla::TimeStamp startTime, stopTime;
1697 uint32_t size = events_.size();
1698 for (size_t i = 0; i < size; i++) {
1699 uint32_t textId = events_[i].textId;
1700
1701 // Record any log events that have no durations such as Bailouts with a
1702 // value of 1. Make sure the funcStack actually has something in it or
1703 // else the Bailout event will not be associated with any script. This
1704 // can commonly occur when profiling & tracing starts since we may have
1705 // already past the point where the script event is created.
1706 if (TLTextIdIsLogEvent(textId) && !funcStack.empty()) {
1707 UniqueChars script =
1708 traceLoggerState->getFullScriptName(funcStack.back());
1709 if (!updateScriptMap(map, script.release(), textId, 1)) {
1710 return false;
1711 }
1712 }
1713
1714 // Hit a new tree event or a stop event, so add (new event timestamp - old
1715 // event timestamp) to the old event's self-time.
1716 if (TLTextIdIsTreeEvent(textId)) {
1717 if (!eventStack.empty()) {
1718 stopTime = events_[i].time;
1719
1720 uint32_t deltaTime =
1721 static_cast<uint32_t>((stopTime - startTime).ToMicroseconds());
1722 eventStack.back()->time += deltaTime;
1723
1724 if (TLTextIdIsEnumEvent(eventStack.back()->textId)) {
1725 if (!funcStack.empty() && !eventStack.empty()) {
1726 UniqueChars script =
1727 traceLoggerState->getFullScriptName(funcStack.back());
1728 if (!updateScriptMap(map, script.release(),
1729 eventStack.back()->textId, deltaTime)) {
1730 return false;
1731 }
1732 }
1733 }
1734 }
1735
1736 if (TLTextIdIsScriptEvent(textId)) {
1737 if (!funcStack.append(textId)) {
1738 return false;
1739 }
1740 }
1741
1742 eventInfo* entry = js_new<eventInfo>(textId);
1743 entry->start = events_[i].time;
1744 if (!eventStack.append(entry)) {
1745 return false;
1746 }
1747
1748 startTime = events_[i].time;
1749
1750 } else if (textId == TraceLogger_Stop) {
1751 if (!eventStack.empty()) {
1752 stopTime = events_[i].time;
1753
1754 uint32_t deltaTime =
1755 static_cast<uint32_t>((stopTime - startTime).ToMicroseconds());
1756 eventInfo* entry = eventStack.popCopy();
1757
1758 uint32_t topId = entry->textId;
1759 entry->time += deltaTime;
1760
1761 if (TLTextIdIsEnumEvent(topId)) {
1762 // funcStack will usually be empty near the beginning of a profiling
1763 // session since we may have skipped the point where the script event
1764 // is created. If that's the case, then skip this event since we
1765 // cannot associate it with any script.
1766 if (!funcStack.empty()) {
1767 UniqueChars script =
1768 traceLoggerState->getFullScriptName(funcStack.back());
1769 if (!updateScriptMap(map, script.release(), topId, deltaTime)) {
1770 return false;
1771 }
1772 }
1773 }
1774 js_delete(entry);
1775
1776 if (TLTextIdIsScriptEvent(topId) && !funcStack.empty()) {
1777 funcStack.popBack();
1778 }
1779
1780 startTime = events_[i].time;
1781 }
1782 }
1783 }
1784
1785 return true;
1786 }
1787
ResetTraceLogger(void)1788 JS_PUBLIC_API void JS::ResetTraceLogger(void) { js::ResetTraceLogger(); }
1789
SpewTraceLoggerThread(JSContext * cx)1790 JS_PUBLIC_API void JS::SpewTraceLoggerThread(JSContext* cx) {
1791 if (!traceLoggerState) {
1792 return;
1793 }
1794
1795 if (cx && cx->traceLogger) {
1796 cx->traceLogger->spewTraceLoggerStats();
1797 }
1798 }
1799
SpewTraceLoggerForCurrentProcess()1800 JS_PUBLIC_API void JS::SpewTraceLoggerForCurrentProcess() {
1801 if (!traceLoggerState) {
1802 return;
1803 }
1804 traceLoggerState->spewTraceLoggerStats();
1805 }
1806
StartTraceLogger(JSContext * cx)1807 JS_PUBLIC_API void JS::StartTraceLogger(JSContext* cx) {
1808 if (!traceLoggerState) {
1809 return;
1810 }
1811
1812 if (!jit::JitOptions.enableTraceLogger) {
1813 LockGuard<Mutex> guard(traceLoggerState->lock);
1814 traceLoggerState->enableTextIdsForProfiler();
1815 jit::JitOptions.enableTraceLogger = true;
1816 traceLoggerState->startTime = mozilla::TimeStamp::Now();
1817 }
1818
1819 TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
1820 if (logger) {
1821 logger->enable();
1822 }
1823 }
1824
StopTraceLogger(JSContext * cx)1825 JS_PUBLIC_API void JS::StopTraceLogger(JSContext* cx) {
1826 if (!traceLoggerState) {
1827 return;
1828 }
1829
1830 if (jit::JitOptions.enableTraceLogger) {
1831 LockGuard<Mutex> guard(traceLoggerState->lock);
1832 traceLoggerState->disableTextIdsForProfiler();
1833 jit::JitOptions.enableTraceLogger = false;
1834 }
1835
1836 TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
1837 if (logger) {
1838 logger->disable();
1839 }
1840 }
1841