/* -*- 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/. */ #ifndef jit_StructuredSpewer_h #define jit_StructuredSpewer_h #ifdef JS_STRUCTURED_SPEW # include "mozilla/Atomics.h" # include "mozilla/Attributes.h" # include "mozilla/EnumeratedArray.h" # include "mozilla/EnumSet.h" # include "mozilla/Maybe.h" # include "mozilla/Sprintf.h" # include "jstypes.h" # include "vm/JSONPrinter.h" # include "vm/Printer.h" # ifdef XP_WIN # include # define getpid _getpid # else # include # endif // [SMDOC] JSON Structured Spewer // // This spewer design has two goals: // // 1. Provide a spew mechanism that has first-class support for slicing and // dicing output. This means that filtering by script and channel should be // the dominant output mechanism. // 2. Provide a simple powerful mechanism for getting information out of the // compiler and into tools. I'm inspired by tools like CacheIR analyzer, // IR Hydra, and the upcoming tracelogger integration into // profiler.firefox.com. // // The spewer has four main control knobs, all currently set as // environment variables. All but the first are optional. When the spewer is // activated through the browser, it is synchronized with the gecko profiler // start and stop routines. Setting SPEW=AtStartup activates the spewer at // startup instead of profiler start, but profiler stop will still deactivate // the spewer. // // SPEW: Activates the spewer. The value provided is interpreted as a comma // separated list that selects channels by name. Currently there's no // mapping between internal and external names, so the channel names // are exactly those described in STRUCTURED_CHANNEL_LIST below. // // SPEW_FILE: Selects the file to write to. An absolute path. // // SPEW_FILTER: A string which is matched against 'signature' constructed or a // JSScript, currently connsisting of filename:line:col. // // Matching in this version is merely finding the string in // in question in the 'signature' // // SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR, output goes // to $MOZ_UPLOAD_DIR/spew_output* to ease usage with Treeherder. // // Other notes: // - Thread safety is provided by opening a new spewer file for every thread. // - Each file is prefixed with the PID to handle multiple processes. // - Files are opened lazily, just before the first write to them. class JS_PUBLIC_API JSScript; namespace js { # define STRUCTURED_CHANNEL_LIST(_) \ _(BaselineICStats) \ _(ScriptStats) \ _(RateMyCacheIR) // Structured spew channels enum class SpewChannel { # define STRUCTURED_CHANNEL(name) name, STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL) # undef STRUCTURED_CHANNEL Count, Disabled }; // A filter is used to select what channel is enabled // // To save memory, JSScripts do not have their own filters, but instead have // a single bit which tracks if that script has opted into spewing. class StructuredSpewFilter { // Indicates what spew channel is enabled. SpewChannel channel_ = SpewChannel::Disabled; public: // Return true iff any channel is enabled. bool isChannelSelected() const { return !(channel_ == SpewChannel::Disabled); } // Return true iff spew is enabled for this channel for // the script this was created for. bool enabled(SpewChannel x) const { return channel_ == x; } // Returns true if we have enabled a new channel, false otherwise. bool enableChannel(SpewChannel x) { // Assert that we are not going to set the channel to // SpewChannel::Disabled. MOZ_ASSERT(x != SpewChannel::Disabled); if (!isChannelSelected()) { channel_ = x; return true; } return false; } void disableAllChannels() { channel_ = SpewChannel::Disabled; } }; class StructuredSpewer { public: StructuredSpewer() : outputInitializationAttempted_(false), spewingEnabled_(0), json_(mozilla::Nothing()), selectedChannel_() { if (getenv("SPEW")) { parseSpewFlags(getenv("SPEW")); } } ~StructuredSpewer() { if (json_.isSome()) { json_->endList(); output_.flush(); output_.finish(); json_.reset(); } } void enableSpewing() { spewingEnabled_++; } void disableSpewing() { MOZ_ASSERT(spewingEnabled_ > 0); spewingEnabled_--; } // Check if the spewer is enabled for a particular script, used to power // script level filtering. bool enabled(JSScript* script); // A generic printf like spewer that logs the formatted string. static void spew(JSContext* cx, SpewChannel channel, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4); // Returns true iff the channel is enabled for the given script. bool enabled(JSContext* cx, const JSScript* script, SpewChannel channel) const; private: // In order to support lazy initialization, and simultaneously support a // failure to open a log file being non-fatal (as lazily reporting failure // would be hard, we have an akward set of states to represent. // // We need to handle: // - Output file not initialized, and not yet attempted // - Output file not intialized, attempted, and failed. // - Output file initialized, JSON writer ready for input. // // Because Fprinter doesn't record whether or not its initialization was // attempted, we keep track of that here. // // The contract we require is that ensureInitializationAttempted() be called // just before any attempte to write. This will ensure the file open is // attemped in the right place. bool outputInitializationAttempted_; // Indicates the number of times spewing has been enabled. If // spewingEnabled_ is greater than zero, then spewing is enabled. size_t spewingEnabled_; Fprinter output_; mozilla::Maybe json_; // Globally selected channel. StructuredSpewFilter selectedChannel_; using NameArray = mozilla::EnumeratedArray; // Channel Names static NameArray const names_; // Get channel name static const char* getName(SpewChannel channel) { return names_[channel]; } // Call just before writes to the output are expected. // // Avoids opening files that will remain empty // // Returns true iff we are able to write now. bool ensureInitializationAttempted(); void tryToInitializeOutput(const char* path); // Using flags, choose the enabled channels for this spewer. void parseSpewFlags(const char* flags); // Returns true iff the channels is enabled bool enabled(SpewChannel channel) { return (spewingEnabled_ > 0 && selectedChannel_.enabled(channel)); } // Start a record void startObject(JSContext* cx, const JSScript* script, SpewChannel channel); friend class AutoSpewChannel; friend class AutoStructuredSpewer; }; // An RAII class for accessing the structured spewer. // // This class prefixes the spew with channel and location information. // // Before writing with this Spewer, it must be checked: ie. // // { // AutoSpew x(...); // if (x) { // x->property("lalala", y); // } // } // // As the selected channel may not be enabled. // // Note: If the lifespan of two AutoSpewers overlap, then the output // may not be well defined JSON. These spewers should be given as // short a lifespan as possible. // // As well, this class cannot be copied or assigned to ensure the // correct number of destructors fire. class MOZ_RAII AutoStructuredSpewer { mozilla::Maybe printer_; AutoStructuredSpewer(const AutoStructuredSpewer&) = delete; void operator=(AutoStructuredSpewer&) = delete; public: explicit AutoStructuredSpewer(JSContext* cx, SpewChannel channel, JSScript* script); ~AutoStructuredSpewer() { if (printer_.isSome()) { printer_.ref()->endObject(); } } explicit operator bool() const { return printer_.isSome(); } JSONPrinter* operator->() { MOZ_ASSERT(printer_.isSome()); return printer_.ref(); } JSONPrinter& operator*() { MOZ_ASSERT(printer_.isSome()); return *printer_.ref(); } }; // An RAII class for setting a structured spewer's channel. // // This class is used to set a spewer's channel and then automatically // unset the channel when AutoSpewChannel goes out of scope. class MOZ_RAII AutoSpewChannel { JSContext* cx_; bool wasChannelAutoSet = false; AutoSpewChannel(const AutoSpewChannel&) = delete; void operator=(AutoSpewChannel&) = delete; public: explicit AutoSpewChannel(JSContext* cx, SpewChannel channel, JSScript* script); ~AutoSpewChannel(); }; } // namespace js #endif #endif /* jit_StructuredSpewer_h */