diff options
Diffstat (limited to 'js/src/util/StructuredSpewer.cpp')
-rw-r--r-- | js/src/util/StructuredSpewer.cpp | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/js/src/util/StructuredSpewer.cpp b/js/src/util/StructuredSpewer.cpp new file mode 100644 index 0000000000..509ffb4e01 --- /dev/null +++ b/js/src/util/StructuredSpewer.cpp @@ -0,0 +1,267 @@ +/* -*- 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/. */ + +#ifdef JS_STRUCTURED_SPEW + +# include "util/StructuredSpewer.h" + +# include "mozilla/Sprintf.h" + +# include "util/GetPidProvider.h" // getpid() +# include "util/Text.h" +# include "vm/JSContext.h" +# include "vm/JSScript.h" + +using namespace js; + +const StructuredSpewer::NameArray StructuredSpewer::names_ = { +# define STRUCTURED_CHANNEL(name) #name, + STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL) +# undef STRUCTURED_CHANNEL +}; + +// Choose a sensible default spew directory. +// +// The preference here is to use the current working directory, +// except on Android. +# ifndef DEFAULT_SPEW_DIRECTORY +# if defined(_WIN32) +# define DEFAULT_SPEW_DIRECTORY "." +# elif defined(__ANDROID__) +# define DEFAULT_SPEW_DIRECTORY "/data/local/tmp" +# else +# define DEFAULT_SPEW_DIRECTORY "." +# endif +# endif + +bool StructuredSpewer::ensureInitializationAttempted() { + if (!outputInitializationAttempted_) { + char filename[2048] = {0}; + // For ease of use with Treeherder + if (getenv("SPEW_UPLOAD") && getenv("MOZ_UPLOAD_DIR")) { + SprintfLiteral(filename, "%s/spew_output", getenv("MOZ_UPLOAD_DIR")); + } else if (getenv("SPEW_FILE")) { + SprintfLiteral(filename, "%s", getenv("SPEW_FILE")); + } else { + SprintfLiteral(filename, "%s/spew_output", DEFAULT_SPEW_DIRECTORY); + } + tryToInitializeOutput(filename); + // We can't use the intialization state of the Fprinter, as it is not + // marked as initialized in a case where we cannot open the output, so + // we track the attempt separately. + outputInitializationAttempted_ = true; + } + + return json_.isSome(); +} + +void StructuredSpewer::tryToInitializeOutput(const char* path) { + static mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> threadCounter; + + char suffix_path[2048] = {0}; + SprintfLiteral(suffix_path, "%s.%d.%u", path, getpid(), threadCounter++); + + if (!output_.init(suffix_path)) { + // Returning here before we've emplaced the JSONPrinter + // means this is effectively disabled, but fail earlier + // we also disable the channel. + selectedChannel_.disableAllChannels(); + return; + } + + // These logs are structured as a JSON array. + json_.emplace(output_); + json_->beginList(); +} + +// Treat pattern like a glob, and return true if pattern exists +// in the script's name or filename or line number. +// +// This is the most basic matching I can imagine +static bool MatchJSScript(JSScript* script, const char* pattern) { + if (!pattern) { + return false; + } + + char signature[2048] = {0}; + SprintfLiteral(signature, "%s:%u:%u", script->filename(), script->lineno(), + script->column()); + + // Trivial containment match. + char* result = strstr(signature, pattern); + + return result != nullptr; +} + +bool StructuredSpewer::enabled(JSScript* script) { + if (spewingEnabled_ == 0) { + return false; + } + + static const char* pattern = getenv("SPEW_FILTER"); + if (!pattern || MatchJSScript(script, pattern)) { + return true; + } + return false; +} + +bool StructuredSpewer::enabled(JSContext* cx, const JSScript* script, + SpewChannel channel) const { + if (script && !script->spewEnabled()) { + return false; + } + return cx->spewer().enabled(channel); +} + +// Attempt to setup a common header for objects based on script/channel. +// +// Returns true if the spewer is prepared for more input +void StructuredSpewer::startObject(JSContext* cx, const JSScript* script, + SpewChannel channel) { + MOZ_ASSERT(json_.isSome()); + + JSONPrinter& json = json_.ref(); + + json.beginObject(); + json.property("channel", getName(channel)); + if (script) { + json.beginObjectProperty("location"); + json.property("filename", script->filename()); + json.property("line", script->lineno()); + json.property("column", script->column()); + json.endObject(); + } +} + +/* static */ +void StructuredSpewer::spew(JSContext* cx, SpewChannel channel, const char* fmt, + ...) { + // Because we don't have a script here, use the singleton's + // filter to determine if the channel is active. + if (!cx->spewer().enabled(channel)) { + return; + } + + if (!cx->spewer().ensureInitializationAttempted()) { + return; + } + + va_list ap; + va_start(ap, fmt); + + MOZ_ASSERT(cx->spewer().json_.isSome()); + + JSONPrinter& json = cx->spewer().json_.ref(); + + json.beginObject(); + json.property("channel", getName(channel)); + json.formatProperty("message", fmt, ap); + json.endObject(); + + va_end(ap); +} + +// Currently uses the exact spew flag representation as text. +void StructuredSpewer::parseSpewFlags(const char* flags) { +# define CHECK_CHANNEL(name) \ + if (ContainsFlag(flags, #name)) { \ + selectedChannel_.enableChannel(SpewChannel::name); \ + break; \ + } + + do { + STRUCTURED_CHANNEL_LIST(CHECK_CHANNEL) + } while (false); + +# undef CHECK_CHANNEL + + if (ContainsFlag(flags, "AtStartup")) { + enableSpewing(); + } + + if (ContainsFlag(flags, "help")) { + printf( + "\n" + "usage: SPEW=option,option,... where options can be:\n" + "\n" + " help Dump this help message\n" + " channel Enable the selected channel from below, " + "if\n" + " more than one channel is specified, then " + "the\n" + " channel will be set whichever specified " + "filter\n" + " comes first in STRUCTURED_CHANNEL_LIST." + " AtStartup Enable spewing at browser startup instead\n" + " of when gecko profiling starts." + "\n" + " Channels: \n" + "\n" + // List Channels + " BaselineICStats Dump the IC Entry counters during Ion " + "analysis\n" + " ScriptStats Dump statistics collected by tracelogger " + "that\n" + " is aggregated by script. Requires\n" + " JS_TRACE_LOGGING=1\n" + " CacheIRHealthReport Dump the CacheIR information and " + "associated " + "rating\n" + // End Channel list + "\n\n" + "By default output goes to a file called spew_output.$PID.$THREAD\n" + "\n" + "Further control of the spewer can be accomplished with the below\n" + "environment variables:\n" + "\n" + " SPEW_FILE: Selects the file to write to. An absolute path.\n" + "\n" + " SPEW_FILTER: A string which is matched against 'signature'\n" + " constructed from a JSScript, currently connsisting of \n" + " filename:line:col.\n" + "\n" + " A JSScript matches the filter string is found in the\n" + " signature\n" + "\n" + " SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR,\n" + " output goes to $MOZ_UPLOAD_DIR/spew_output* to ease usage\n" + " with Treeherder.\n" + + ); + exit(0); + } +} + +AutoStructuredSpewer::AutoStructuredSpewer(JSContext* cx, SpewChannel channel, + JSScript* script) + : printer_(mozilla::Nothing()) { + if (!cx->spewer().enabled(cx, script, channel)) { + return; + } + + if (!cx->spewer().ensureInitializationAttempted()) { + return; + } + + cx->spewer().startObject(cx, script, channel); + printer_.emplace(&cx->spewer().json_.ref()); +} + +AutoSpewChannel::AutoSpewChannel(JSContext* cx, SpewChannel channel, + JSScript* script) + : cx_(cx) { + if (!cx->spewer().enabled(cx, script, channel)) { + wasChannelAutoSet = cx->spewer().selectedChannel_.enableChannel(channel); + } +} + +AutoSpewChannel::~AutoSpewChannel() { + if (wasChannelAutoSet) { + cx_->spewer().selectedChannel_.disableAllChannels(); + } +} + +#endif |