diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/TraceLoggingGraph.cpp | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/js/src/vm/TraceLoggingGraph.cpp b/js/src/vm/TraceLoggingGraph.cpp new file mode 100644 index 0000000000..921c19d3d0 --- /dev/null +++ b/js/src/vm/TraceLoggingGraph.cpp @@ -0,0 +1,721 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/TraceLoggingGraph.h" + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> +#endif + +#include "mozilla/EndianUtils.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ScopeExit.h" + +#include "js/Printf.h" +#include "js/UniquePtr.h" +#include "threading/LockGuard.h" +#include "threading/Thread.h" +#include "util/Text.h" +#include "vm/TraceLogging.h" + +#ifndef DEFAULT_TRACE_LOG_DIR +# if defined(_WIN32) +# define DEFAULT_TRACE_LOG_DIR "." +# else +# define DEFAULT_TRACE_LOG_DIR "/tmp/" +# endif +#endif + +using mozilla::MakeScopeExit; +using mozilla::NativeEndian; + +TraceLoggerGraphState* traceLoggerGraphState = nullptr; + +// gcc and clang have these in symcat.h, but MSVC does not. +#ifndef STRINGX +# define STRINGX(x) # x +#endif +#ifndef XSTRING +# define XSTRING(macro) STRINGX(macro) +#endif + +#define MAX_LOGGERS 999 + +// Return a filename relative to the output directory. %u and %d substitutions +// are allowed, with %u standing for a full 32-bit number and %d standing for +// an up to 3-digit number. +static js::UniqueChars MOZ_FORMAT_PRINTF(1, 2) + AllocTraceLogFilename(const char* pattern, ...) { + va_list ap; + + const char* outdir = + getenv("TLDIR") ? getenv("TLDIR") : DEFAULT_TRACE_LOG_DIR; + size_t len = strlen(outdir) + 1; // "+ 1" is for the '/' + + for (const char* p = pattern; *p; p++) { + if (*p == '%') { + p++; + if (*p == 'u') { + len += sizeof("4294967295") - 1; + } else if (*p == 'd') { + len += sizeof(XSTRING(MAX_LOGGERS)) - 1; + } else { + MOZ_CRASH("Invalid format"); + } + } else { + len++; + } + } + + len++; // For the terminating NUL. + + js::UniqueChars filename(js_pod_malloc<char>(len)); + if (!filename) { + return nullptr; + } + char* rest = filename.get() + sprintf(filename.get(), "%s/", outdir); + + va_start(ap, pattern); + int ret = vsnprintf(rest, len, pattern, ap); + va_end(ap); + if (ret < 0) { + return nullptr; + } + + MOZ_ASSERT(size_t(ret) <= len - (strlen(outdir) + 1), + "overran TL filename buffer; %d given too large a value?"); + + return filename; +} + +bool TraceLoggerGraphState::init() { + pid_ = (uint32_t)getpid(); + + js::UniqueChars filename = AllocTraceLogFilename("tl-data.%u.json", pid_); + out = fopen(filename.get(), "w"); + if (!out) { + fprintf(stderr, "warning: failed to create TraceLogger output file %s\n", + filename.get()); + return false; + } + + fprintf(out, "["); + + // Write the latest tl-data.*.json file to tl-data.json. + // In most cases that is the wanted file. + js::UniqueChars masterFilename = AllocTraceLogFilename("tl-data.json"); + if (FILE* last = fopen(masterFilename.get(), "w")) { + char* basename = strrchr(filename.get(), '/'); + basename = basename ? basename + 1 : filename.get(); + fprintf(last, "\"%s\"", basename); + fclose(last); + } + +#ifdef DEBUG + initialized = true; +#endif + return true; +} + +TraceLoggerGraphState::~TraceLoggerGraphState() { + if (out) { + fprintf(out, "]"); + fclose(out); + out = nullptr; + } + +#ifdef DEBUG + initialized = false; +#endif +} + +uint32_t TraceLoggerGraphState::nextLoggerId() { + js::LockGuard<js::Mutex> guard(lock); + + MOZ_ASSERT(initialized); + + if (numLoggers > MAX_LOGGERS) { + fputs("TraceLogging: Can't create more than " XSTRING( + MAX_LOGGERS) " different loggers.", + stderr); + return uint32_t(-1); + } + + if (numLoggers > 0) { + int written = fprintf(out, ",\n"); + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + return uint32_t(-1); + } + } + + int written = fprintf( + out, + "{\"tree\":\"tl-tree.%u.%u.tl\", \"events\":\"tl-event.%u.%u.tl\", " + "\"dict\":\"tl-dict.%u.%u.json\", \"treeFormat\":\"64,64,31,1,32\"", + pid_, numLoggers, pid_, numLoggers, pid_, numLoggers); + + if (written > 0) { + char threadName[16]; + js::ThisThread::GetName(threadName, sizeof(threadName)); + if (threadName[0]) { + written = fprintf(out, R"(, "threadName":"%s")", threadName); + } + } + + if (written > 0) { + written = fprintf(out, "}"); + } + + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + return uint32_t(-1); + } + + return numLoggers++; +} + +size_t TraceLoggerGraphState::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + return 0; +} + +static bool EnsureTraceLoggerGraphState() { + if (MOZ_LIKELY(traceLoggerGraphState)) { + return true; + } + + traceLoggerGraphState = js_new<TraceLoggerGraphState>(); + if (!traceLoggerGraphState) { + return false; + } + + if (!traceLoggerGraphState->init()) { + js::DestroyTraceLoggerGraphState(); + return false; + } + + return true; +} + +size_t js::SizeOfTraceLogGraphState(mozilla::MallocSizeOf mallocSizeOf) { + return traceLoggerGraphState + ? traceLoggerGraphState->sizeOfIncludingThis(mallocSizeOf) + : 0; +} + +void js::DestroyTraceLoggerGraphState() { + if (traceLoggerGraphState) { + js_delete(traceLoggerGraphState); + traceLoggerGraphState = nullptr; + } +} + +bool TraceLoggerGraph::init(uint64_t startTimestamp, bool graphFileEnabled) { + auto fail = MakeScopeExit([&] { failed = true; }); + + if (graphFileEnabled) { + if (!EnsureTraceLoggerGraphState()) { + return false; + } + + uint32_t loggerId = traceLoggerGraphState->nextLoggerId(); + if (loggerId == uint32_t(-1)) { + return false; + } + + uint32_t pid = traceLoggerGraphState->pid(); + + js::UniqueChars dictFilename = + AllocTraceLogFilename("tl-dict.%u.%u.json", pid, loggerId); + dictFile = fopen(dictFilename.get(), "w"); + if (!dictFile) { + return false; + } + auto cleanupDict = MakeScopeExit([&] { + fclose(dictFile); + dictFile = nullptr; + }); + + js::UniqueChars treeFilename = + AllocTraceLogFilename("tl-tree.%u.%u.tl", pid, loggerId); + treeFile = fopen(treeFilename.get(), "w+b"); + if (!treeFile) { + return false; + } + auto cleanupTree = MakeScopeExit([&] { + fclose(treeFile); + treeFile = nullptr; + }); + + js::UniqueChars eventFilename = + AllocTraceLogFilename("tl-event.%u.%u.tl", pid, loggerId); + eventFile = fopen(eventFilename.get(), "wb"); + if (!eventFile) { + return false; + } + auto cleanupEvent = MakeScopeExit([&] { + fclose(eventFile); + eventFile = nullptr; + }); + + if (fprintf(dictFile, "[") < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + return false; + } + + cleanupDict.release(); + cleanupTree.release(); + cleanupEvent.release(); + } + + if (!tree.init()) { + return false; + } + if (!stack.init()) { + return false; + } + + // Create the top tree node and corresponding first stack item. + TreeEntry& treeEntry = tree.pushUninitialized(); + treeEntry.setStart(startTimestamp); + treeEntry.setStop(0); + treeEntry.setTextId(0); + treeEntry.setHasChildren(false); + treeEntry.setNextId(0); + + StackEntry& stackEntry = stack.pushUninitialized(); + stackEntry.setTreeId(0); + stackEntry.setLastChildId(0); + stackEntry.setActive(true); + + fail.release(); + + return true; +} + +TraceLoggerGraph::~TraceLoggerGraph() { + // Write dictionary to disk + if (dictFile) { + int written = fprintf(dictFile, "]"); + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + } + fclose(dictFile); + + dictFile = nullptr; + } + + if (!failed && treeFile) { + // Make sure every start entry has a corresponding stop value. + // We temporarily enable logging for this. Stop doesn't need any extra data, + // so is safe to do even when we have encountered OOM. + enabled = true; + while (stack.size() > 1) { + stopEvent(0); + } + enabled = false; + } + + if (!failed && !flush()) { + fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); + enabled = false; + failed = true; + } + + if (treeFile) { + fclose(treeFile); + treeFile = nullptr; + } + + if (eventFile) { + fclose(eventFile); + eventFile = nullptr; + } +} + +bool TraceLoggerGraph::flush() { + MOZ_ASSERT(!failed); + + if (treeFile) { + // Format data in big endian. + for (size_t i = 0; i < tree.size(); i++) { + entryToBigEndian(&tree[i]); + } + + int success = fseek(treeFile, 0, SEEK_END); + if (success != 0) { + return false; + } + + size_t bytesWritten = + fwrite(tree.data(), sizeof(TreeEntry), tree.size(), treeFile); + if (bytesWritten < tree.size()) { + return false; + } + + treeOffset += tree.size(); + tree.clear(); + } + + return true; +} + +void TraceLoggerGraph::entryToBigEndian(TreeEntry* entry) { + entry->start_ = NativeEndian::swapToBigEndian(entry->start_); + entry->stop_ = NativeEndian::swapToBigEndian(entry->stop_); + uint32_t data = (entry->u.s.textId_ << 1) + entry->u.s.hasChildren_; + entry->u.value_ = NativeEndian::swapToBigEndian(data); + entry->nextId_ = NativeEndian::swapToBigEndian(entry->nextId_); +} + +void TraceLoggerGraph::entryToSystemEndian(TreeEntry* entry) { + entry->start_ = NativeEndian::swapFromBigEndian(entry->start_); + entry->stop_ = NativeEndian::swapFromBigEndian(entry->stop_); + + uint32_t data = NativeEndian::swapFromBigEndian(entry->u.value_); + entry->u.s.textId_ = data >> 1; + entry->u.s.hasChildren_ = data & 0x1; + + entry->nextId_ = NativeEndian::swapFromBigEndian(entry->nextId_); +} + +void TraceLoggerGraph::startEvent(uint32_t id, uint64_t timestamp) { + if (failed || enabled == 0) { + return; + } + + if (!tree.hasSpaceForAdd()) { + if (tree.size() >= treeSizeFlushLimit() || !tree.ensureSpaceBeforeAdd()) { + if (!treeFile) { + disable(timestamp); + return; + } + + if (!flush()) { + fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); + enabled = false; + failed = true; + return; + } + } + } + + if (!startEventInternal(id, timestamp)) { + fprintf(stderr, "TraceLogging: Failed to start an event.\n"); + enabled = false; + failed = true; + return; + } +} + +TraceLoggerGraph::StackEntry& TraceLoggerGraph::getActiveAncestor() { + uint32_t parentId = stack.lastEntryId(); + while (!stack[parentId].active()) { + parentId--; + } + return stack[parentId]; +} + +bool TraceLoggerGraph::startEventInternal(uint32_t id, uint64_t timestamp) { + if (!stack.ensureSpaceBeforeAdd()) { + return false; + } + + // Patch up the tree to be correct. There are two scenarios: + // 1) Parent has no children yet. So update parent to include children. + // 2) Parent has already children. Update last child to link to the new + // child. + StackEntry& parent = getActiveAncestor(); +#ifdef DEBUG + TreeEntry entry; + if (!getTreeEntry(parent.treeId(), &entry)) { + return false; + } +#endif + + if (parent.lastChildId() == 0) { + MOZ_ASSERT(!entry.hasChildren()); + MOZ_ASSERT(parent.treeId() == treeOffset + tree.size() - 1); + + if (!updateHasChildren(parent.treeId())) { + return false; + } + } else { + MOZ_ASSERT(entry.hasChildren()); + + if (!updateNextId(parent.lastChildId(), tree.size() + treeOffset)) { + return false; + } + } + + // Add a new tree entry. + TreeEntry& treeEntry = tree.pushUninitialized(); + treeEntry.setStart(timestamp); + treeEntry.setStop(0); + treeEntry.setTextId(id); + treeEntry.setHasChildren(false); + treeEntry.setNextId(0); + + // Add a new stack entry. + StackEntry& stackEntry = stack.pushUninitialized(); + stackEntry.setTreeId(tree.lastEntryId() + treeOffset); + stackEntry.setLastChildId(0); + stackEntry.setActive(true); + + // Set the last child of the parent to this newly added entry. + parent.setLastChildId(tree.lastEntryId() + treeOffset); + + return true; +} + +void TraceLoggerGraph::stopEvent(uint32_t id, uint64_t timestamp) { +#ifdef DEBUG + if (id != TraceLogger_Scripts && id != TraceLogger_Engine && + stack.size() > 1 && stack.lastEntry().active()) { + TreeEntry entry; + MOZ_ASSERT(getTreeEntry(stack.lastEntry().treeId(), &entry)); + MOZ_ASSERT(entry.textId() == id); + } +#endif + + stopEvent(timestamp); +} + +void TraceLoggerGraph::stopEvent(uint64_t timestamp) { + if (enabled && stack.lastEntry().active()) { + if (!updateStop(stack.lastEntry().treeId(), timestamp)) { + fprintf(stderr, "TraceLogging: Failed to stop an event.\n"); + enabled = false; + failed = true; + return; + } + } + if (stack.size() == 1) { + if (!enabled) { + return; + } + + // Forcefully disable logging. We have no stack information anymore. + logTimestamp(TraceLogger_Disable, timestamp); + return; + } + stack.pop(); +} + +void TraceLoggerGraph::logTimestamp(uint32_t id, uint64_t timestamp) { + if (failed) { + return; + } + + if (id == TraceLogger_Enable) { + enabled = true; + } + + if (!enabled) { + return; + } + + if (id == TraceLogger_Disable) { + disable(timestamp); + } + + if (!eventFile) { + return; + } + + // Format data in big endian + timestamp = NativeEndian::swapToBigEndian(timestamp); + id = NativeEndian::swapToBigEndian(id); + + // The layout of the event log in the log file is: + // [timestamp, textId] + size_t itemsWritten = 0; + itemsWritten += fwrite(×tamp, sizeof(uint64_t), 1, eventFile); + itemsWritten += fwrite(&id, sizeof(uint32_t), 1, eventFile); + if (itemsWritten < 2) { + failed = true; + enabled = false; + } +} + +bool TraceLoggerGraph::getTreeEntry(uint32_t treeId, TreeEntry* entry) { + // Entry is still in memory + if (treeId >= treeOffset) { + *entry = tree[treeId - treeOffset]; + return true; + } + + // If treeFile is null and treeOffset is non-zero then something is wrong + if (!treeFile) { + return false; + } + + // Entry has been flushed to disk. Look it up. + int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); + if (success != 0) { + return false; + } + + size_t itemsRead = fread((void*)entry, sizeof(TreeEntry), 1, treeFile); + if (itemsRead < 1) { + return false; + } + + entryToSystemEndian(entry); + return true; +} + +bool TraceLoggerGraph::saveTreeEntry(uint32_t treeId, TreeEntry* entry) { + MOZ_ASSERT(treeFile != nullptr); + int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); + if (success != 0) { + return false; + } + + entryToBigEndian(entry); + + size_t itemsWritten = fwrite(entry, sizeof(TreeEntry), 1, treeFile); + if (itemsWritten < 1) { + return false; + } + + return true; +} + +bool TraceLoggerGraph::updateHasChildren(uint32_t treeId, bool hasChildren) { + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) { + return false; + } + entry.setHasChildren(hasChildren); + if (treeFile && !saveTreeEntry(treeId, &entry)) { + return false; + } + return true; + } + + tree[treeId - treeOffset].setHasChildren(hasChildren); + return true; +} + +bool TraceLoggerGraph::updateNextId(uint32_t treeId, uint32_t nextId) { + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) { + return false; + } + entry.setNextId(nextId); + if (treeFile && !saveTreeEntry(treeId, &entry)) { + return false; + } + return true; + } + + tree[treeId - treeOffset].setNextId(nextId); + return true; +} + +bool TraceLoggerGraph::updateStop(uint32_t treeId, uint64_t timestamp) { + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) { + return false; + } + entry.setStop(timestamp); + if (treeFile && !saveTreeEntry(treeId, &entry)) { + return false; + } + return true; + } + + tree[treeId - treeOffset].setStop(timestamp); + return true; +} + +void TraceLoggerGraph::disable(uint64_t timestamp) { + MOZ_ASSERT(enabled); + while (stack.size() > 1) { + stopEvent(timestamp); + } + + enabled = false; +} + +void TraceLoggerGraph::log(ContinuousSpace<EventEntry>& events, + mozilla::TimeStamp startTime) { + for (uint32_t i = 0; i < events.size(); i++) { + mozilla::TimeDuration delta = events[i].time - startTime; + uint64_t timeOffset = static_cast<uint64_t>(delta.ToMicroseconds()); + + if (events[i].textId == TraceLogger_Stop) { + stopEvent(timeOffset); + } else if (TLTextIdIsTreeEvent(events[i].textId)) { + startEvent(events[i].textId, timeOffset); + } else { + logTimestamp(events[i].textId, timeOffset); + } + } +} + +void TraceLoggerGraph::addTextId(uint32_t id, const char* text) { + mozilla::Maybe<uint32_t> line = mozilla::Nothing(); + mozilla::Maybe<uint32_t> column = mozilla::Nothing(); + addTextId(id, text, line, column); +} + +void TraceLoggerGraph::addTextId(uint32_t id, const char* text, + mozilla::Maybe<uint32_t>& line, + mozilla::Maybe<uint32_t>& column) { + if (failed) { + return; + } + + // Assume ids are given in order. Which is currently true. + MOZ_ASSERT(id == nextTextId_); + nextTextId_++; + + if (id > 0) { + int written = fprintf(dictFile, ",\n"); + if (written < 0) { + failed = true; + return; + } + } + + js::UniqueChars str; + if (line && column) { + str = JS_smprintf("script %s:%u:%u", text, *line, *column); + } else if (line) { + str = JS_smprintf("script %s:%u", text, *line); + } else { + str = JS_smprintf("%s", text); + } + + if (!js::FileEscapedString(dictFile, str.get(), strlen(str.get()), '"')) { + failed = true; + } + str.reset(); +} + +size_t TraceLoggerGraph::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t size = 0; + size += tree.sizeOfExcludingThis(mallocSizeOf); + size += stack.sizeOfExcludingThis(mallocSizeOf); + return size; +} + +size_t TraceLoggerGraph::sizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); +} + +#undef getpid |