StructuredSpewer.h (9086B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef jit_StructuredSpewer_h 8 #define jit_StructuredSpewer_h 9 10 #ifdef JS_STRUCTURED_SPEW 11 12 # include "mozilla/Attributes.h" 13 # include "mozilla/EnumeratedArray.h" 14 # include "mozilla/Maybe.h" 15 # include "mozilla/Sprintf.h" 16 17 # include "jstypes.h" 18 # include "js/Printer.h" 19 # include "vm/JSONPrinter.h" 20 21 # ifdef XP_WIN 22 # include <process.h> 23 # define getpid _getpid 24 # else 25 # include <unistd.h> 26 # endif 27 28 // [SMDOC] JSON Structured Spewer 29 // 30 // This spewer design has two goals: 31 // 32 // 1. Provide a spew mechanism that has first-class support for slicing and 33 // dicing output. This means that filtering by script and channel should be 34 // the dominant output mechanism. 35 // 2. Provide a simple powerful mechanism for getting information out of the 36 // compiler and into tools. I'm inspired by tools like CacheIR analyzer, 37 // IR Hydra, and the upcoming tracelogger integration into 38 // profiler.firefox.com. 39 // 40 // The spewer has four main control knobs, all currently set as 41 // environment variables. All but the first are optional. When the spewer is 42 // activated through the browser, it is synchronized with the gecko profiler 43 // start and stop routines. Setting SPEW=AtStartup activates the spewer at 44 // startup instead of profiler start, but profiler stop will still deactivate 45 // the spewer. 46 // 47 // SPEW: Activates the spewer. The value provided is interpreted as a comma 48 // separated list that selects channels by name. Currently there's no 49 // mapping between internal and external names, so the channel names 50 // are exactly those described in STRUCTURED_CHANNEL_LIST below. 51 // 52 // SPEW_FILE: Selects the file to write to. An absolute path. 53 // 54 // SPEW_FILTER: A string which is matched against 'signature' constructed or a 55 // JSScript, currently connsisting of filename:line:col. 56 // 57 // Matching in this version is merely finding the string in 58 // in question in the 'signature' 59 // 60 // SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR, output goes 61 // to $MOZ_UPLOAD_DIR/spew_output* to ease usage with Treeherder. 62 // 63 // Other notes: 64 // - Thread safety is provided by opening a new spewer file for every thread. 65 // - Each file is prefixed with the PID to handle multiple processes. 66 // - Files are opened lazily, just before the first write to them. 67 68 class JS_PUBLIC_API JSScript; 69 70 namespace js { 71 72 # define STRUCTURED_CHANNEL_LIST(_) \ 73 _(BaselineICStats) \ 74 _(CacheIRHealthReport) 75 76 // Structured spew channels 77 enum class SpewChannel { 78 # define STRUCTURED_CHANNEL(name) name, 79 STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL) 80 # undef STRUCTURED_CHANNEL 81 Count, 82 Disabled 83 }; 84 85 // A filter is used to select what channel is enabled 86 // 87 // To save memory, JSScripts do not have their own filters, but instead have 88 // a single bit which tracks if that script has opted into spewing. 89 class StructuredSpewFilter { 90 // Indicates what spew channel is enabled. 91 SpewChannel channel_ = SpewChannel::Disabled; 92 93 public: 94 // Return true iff any channel is enabled. 95 bool isChannelSelected() const { 96 return !(channel_ == SpewChannel::Disabled); 97 } 98 99 // Return true iff spew is enabled for this channel for 100 // the script this was created for. 101 bool enabled(SpewChannel x) const { return channel_ == x; } 102 103 // Returns true if we have enabled a new channel, false otherwise. 104 bool enableChannel(SpewChannel x) { 105 // Assert that we are not going to set the channel to 106 // SpewChannel::Disabled. 107 MOZ_ASSERT(x != SpewChannel::Disabled); 108 if (!isChannelSelected()) { 109 channel_ = x; 110 return true; 111 } 112 113 return false; 114 } 115 116 void disableAllChannels() { channel_ = SpewChannel::Disabled; } 117 }; 118 119 class StructuredSpewer { 120 public: 121 StructuredSpewer() 122 : outputInitializationAttempted_(false), 123 spewingEnabled_(0), 124 json_(mozilla::Nothing()), 125 selectedChannel_() { 126 if (getenv("SPEW")) { 127 parseSpewFlags(getenv("SPEW")); 128 } 129 } 130 131 ~StructuredSpewer() { 132 if (json_.isSome()) { 133 json_->endList(); 134 output_.flush(); 135 output_.finish(); 136 json_.reset(); 137 } 138 } 139 140 void enableSpewing() { spewingEnabled_++; } 141 142 void disableSpewing() { 143 MOZ_ASSERT(spewingEnabled_ > 0); 144 spewingEnabled_--; 145 } 146 147 // Check if the spewer is enabled for a particular script, used to power 148 // script level filtering. 149 bool enabled(JSScript* script); 150 151 // A generic printf like spewer that logs the formatted string. 152 static void spew(JSContext* cx, SpewChannel channel, const char* fmt, ...) 153 MOZ_FORMAT_PRINTF(3, 4); 154 155 // Returns true iff the channel is enabled for the given script. 156 bool enabled(JSContext* cx, const JSScript* script, 157 SpewChannel channel) const; 158 159 private: 160 // In order to support lazy initialization, and simultaneously support a 161 // failure to open a log file being non-fatal (as lazily reporting failure 162 // would be hard, we have an akward set of states to represent. 163 // 164 // We need to handle: 165 // - Output file not initialized, and not yet attempted 166 // - Output file not intialized, attempted, and failed. 167 // - Output file initialized, JSON writer ready for input. 168 // 169 // Because Fprinter doesn't record whether or not its initialization was 170 // attempted, we keep track of that here. 171 // 172 // The contract we require is that ensureInitializationAttempted() be called 173 // just before any attempte to write. This will ensure the file open is 174 // attemped in the right place. 175 bool outputInitializationAttempted_; 176 177 // Indicates the number of times spewing has been enabled. If 178 // spewingEnabled_ is greater than zero, then spewing is enabled. 179 size_t spewingEnabled_; 180 181 Fprinter output_; 182 mozilla::Maybe<JSONPrinter> json_; 183 184 // Globally selected channel. 185 StructuredSpewFilter selectedChannel_; 186 187 using NameArray = mozilla::EnumeratedArray<SpewChannel, const char*, 188 size_t(SpewChannel::Count)>; 189 // Channel Names 190 static NameArray const names_; 191 192 // Get channel name 193 static const char* getName(SpewChannel channel) { return names_[channel]; } 194 195 // Call just before writes to the output are expected. 196 // 197 // Avoids opening files that will remain empty 198 // 199 // Returns true iff we are able to write now. 200 bool ensureInitializationAttempted(); 201 202 void tryToInitializeOutput(const char* path); 203 204 // Using flags, choose the enabled channels for this spewer. 205 void parseSpewFlags(const char* flags); 206 207 // Returns true iff the channels is enabled 208 bool enabled(SpewChannel channel) { 209 return (spewingEnabled_ > 0 && selectedChannel_.enabled(channel)); 210 } 211 212 // Start a record 213 void startObject(JSContext* cx, const JSScript* script, SpewChannel channel); 214 215 friend class AutoSpewChannel; 216 friend class AutoStructuredSpewer; 217 }; 218 219 // An RAII class for accessing the structured spewer. 220 // 221 // This class prefixes the spew with channel and location information. 222 // 223 // Before writing with this Spewer, it must be checked: ie. 224 // 225 // { 226 // AutoSpew x(...); 227 // if (x) { 228 // x->property("lalala", y); 229 // } 230 // } 231 // 232 // As the selected channel may not be enabled. 233 // 234 // Note: If the lifespan of two AutoSpewers overlap, then the output 235 // may not be well defined JSON. These spewers should be given as 236 // short a lifespan as possible. 237 // 238 // As well, this class cannot be copied or assigned to ensure the 239 // correct number of destructors fire. 240 class MOZ_RAII AutoStructuredSpewer { 241 mozilla::Maybe<JSONPrinter*> printer_; 242 AutoStructuredSpewer(const AutoStructuredSpewer&) = delete; 243 void operator=(AutoStructuredSpewer&) = delete; 244 245 public: 246 explicit AutoStructuredSpewer(JSContext* cx, SpewChannel channel, 247 JSScript* script); 248 249 ~AutoStructuredSpewer() { 250 if (printer_.isSome()) { 251 printer_.ref()->endObject(); 252 } 253 } 254 255 explicit operator bool() const { return printer_.isSome(); } 256 257 JSONPrinter* operator->() { 258 MOZ_ASSERT(printer_.isSome()); 259 return printer_.ref(); 260 } 261 262 JSONPrinter& operator*() { 263 MOZ_ASSERT(printer_.isSome()); 264 return *printer_.ref(); 265 } 266 }; 267 268 // An RAII class for setting a structured spewer's channel. 269 // 270 // This class is used to set a spewer's channel and then automatically 271 // unset the channel when AutoSpewChannel goes out of scope. 272 class MOZ_RAII AutoSpewChannel { 273 JSContext* cx_; 274 bool wasChannelAutoSet = false; 275 276 AutoSpewChannel(const AutoSpewChannel&) = delete; 277 void operator=(AutoSpewChannel&) = delete; 278 279 public: 280 explicit AutoSpewChannel(JSContext* cx, SpewChannel channel, 281 JSScript* script); 282 283 ~AutoSpewChannel(); 284 }; 285 286 } // namespace js 287 288 #endif 289 #endif /* jit_StructuredSpewer_h */