/* -*- 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 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