summaryrefslogtreecommitdiffstats
path: root/js/src/util/StructuredSpewer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/util/StructuredSpewer.cpp')
-rw-r--r--js/src/util/StructuredSpewer.cpp267
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