StructuredSpewer.cpp (8019B)
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 #ifdef JS_STRUCTURED_SPEW 8 9 # include "util/StructuredSpewer.h" 10 11 # include "mozilla/Sprintf.h" 12 13 # include "util/GetPidProvider.h" // getpid() 14 # include "util/Text.h" 15 # include "vm/JSContext.h" 16 # include "vm/JSScript.h" 17 18 using namespace js; 19 20 const StructuredSpewer::NameArray StructuredSpewer::names_ = { 21 # define STRUCTURED_CHANNEL(name) #name, 22 STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL) 23 # undef STRUCTURED_CHANNEL 24 }; 25 26 // Choose a sensible default spew directory. 27 // 28 // The preference here is to use the current working directory, 29 // except on Android. 30 # ifndef DEFAULT_SPEW_DIRECTORY 31 # if defined(_WIN32) 32 # define DEFAULT_SPEW_DIRECTORY "." 33 # elif defined(__ANDROID__) 34 # define DEFAULT_SPEW_DIRECTORY "/data/local/tmp" 35 # else 36 # define DEFAULT_SPEW_DIRECTORY "." 37 # endif 38 # endif 39 40 bool StructuredSpewer::ensureInitializationAttempted() { 41 if (!outputInitializationAttempted_) { 42 char filename[2048] = {0}; 43 // For ease of use with Treeherder 44 if (getenv("SPEW_UPLOAD") && getenv("MOZ_UPLOAD_DIR")) { 45 SprintfLiteral(filename, "%s/spew_output", getenv("MOZ_UPLOAD_DIR")); 46 } else if (getenv("SPEW_FILE")) { 47 SprintfLiteral(filename, "%s", getenv("SPEW_FILE")); 48 } else { 49 SprintfLiteral(filename, "%s/spew_output", DEFAULT_SPEW_DIRECTORY); 50 } 51 tryToInitializeOutput(filename); 52 // We can't use the intialization state of the Fprinter, as it is not 53 // marked as initialized in a case where we cannot open the output, so 54 // we track the attempt separately. 55 outputInitializationAttempted_ = true; 56 } 57 58 return json_.isSome(); 59 } 60 61 void StructuredSpewer::tryToInitializeOutput(const char* path) { 62 static mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> threadCounter; 63 64 char suffix_path[2048] = {0}; 65 SprintfLiteral(suffix_path, "%s.%d.%u", path, getpid(), threadCounter++); 66 67 if (!output_.init(suffix_path)) { 68 // Returning here before we've emplaced the JSONPrinter 69 // means this is effectively disabled, but fail earlier 70 // we also disable the channel. 71 selectedChannel_.disableAllChannels(); 72 return; 73 } 74 75 // These logs are structured as a JSON array. 76 json_.emplace(output_); 77 json_->beginList(); 78 } 79 80 // Treat pattern like a glob, and return true if pattern exists 81 // in the script's name or filename or line number. 82 // 83 // This is the most basic matching I can imagine 84 static bool MatchJSScript(JSScript* script, const char* pattern) { 85 if (!pattern) { 86 return false; 87 } 88 89 char signature[2048] = {0}; 90 SprintfLiteral(signature, "%s:%u:%u", script->filename(), script->lineno(), 91 script->column().oneOriginValue()); 92 93 // Trivial containment match. 94 char* result = strstr(signature, pattern); 95 96 return result != nullptr; 97 } 98 99 bool StructuredSpewer::enabled(JSScript* script) { 100 if (spewingEnabled_ == 0) { 101 return false; 102 } 103 104 static const char* pattern = getenv("SPEW_FILTER"); 105 if (!pattern || MatchJSScript(script, pattern)) { 106 return true; 107 } 108 return false; 109 } 110 111 bool StructuredSpewer::enabled(JSContext* cx, const JSScript* script, 112 SpewChannel channel) const { 113 if (script && !script->spewEnabled()) { 114 return false; 115 } 116 return cx->spewer().enabled(channel); 117 } 118 119 // Attempt to setup a common header for objects based on script/channel. 120 // 121 // Returns true if the spewer is prepared for more input 122 void StructuredSpewer::startObject(JSContext* cx, const JSScript* script, 123 SpewChannel channel) { 124 MOZ_ASSERT(json_.isSome()); 125 126 JSONPrinter& json = json_.ref(); 127 128 json.beginObject(); 129 json.property("channel", getName(channel)); 130 if (script) { 131 json.beginObjectProperty("location"); 132 json.property("filename", script->filename()); 133 json.property("line", script->lineno()); 134 json.property("column", script->column().oneOriginValue()); 135 json.endObject(); 136 } 137 } 138 139 /* static */ 140 void StructuredSpewer::spew(JSContext* cx, SpewChannel channel, const char* fmt, 141 ...) { 142 // Because we don't have a script here, use the singleton's 143 // filter to determine if the channel is active. 144 if (!cx->spewer().enabled(channel)) { 145 return; 146 } 147 148 if (!cx->spewer().ensureInitializationAttempted()) { 149 return; 150 } 151 152 va_list ap; 153 va_start(ap, fmt); 154 155 MOZ_ASSERT(cx->spewer().json_.isSome()); 156 157 JSONPrinter& json = cx->spewer().json_.ref(); 158 159 json.beginObject(); 160 json.property("channel", getName(channel)); 161 json.formatPropertyVA("message", fmt, ap); 162 json.endObject(); 163 164 va_end(ap); 165 } 166 167 // Currently uses the exact spew flag representation as text. 168 void StructuredSpewer::parseSpewFlags(const char* flags) { 169 # define CHECK_CHANNEL(name) \ 170 if (ContainsFlag(flags, #name)) { \ 171 selectedChannel_.enableChannel(SpewChannel::name); \ 172 break; \ 173 } 174 175 do { 176 STRUCTURED_CHANNEL_LIST(CHECK_CHANNEL) 177 } while (false); 178 179 # undef CHECK_CHANNEL 180 181 if (ContainsFlag(flags, "AtStartup")) { 182 enableSpewing(); 183 } 184 185 if (ContainsFlag(flags, "help")) { 186 // clang-format off 187 printf( 188 "\n" 189 "usage: SPEW=option,option,... where options can be:\n" 190 "\n" 191 " help Dump this help message\n" 192 " channel Enable the selected channel from below, if\n" 193 " more than one channel is specified, then the\n" 194 " channel will be set whichever specified filter\n" 195 " comes first in STRUCTURED_CHANNEL_LIST.\n" 196 " AtStartup Enable spewing at browser startup instead\n" 197 " of when gecko profiling starts." 198 "\n" 199 " Channels: \n" 200 "\n" 201 // List Channels 202 " BaselineICStats Dump the IC Entry counters during Ion analysis\n" 203 " CacheIRHealthReport Dump the CacheIR information and associated rating\n" 204 // End Channel list 205 "\n\n" 206 "By default output goes to a file called spew_output.$PID.$THREAD\n" 207 "\n" 208 "Further control of the spewer can be accomplished with the below\n" 209 "environment variables:\n" 210 "\n" 211 " SPEW_FILE: Selects the file to write to. An absolute path.\n" 212 "\n" 213 " SPEW_FILTER: A string which is matched against 'signature'\n" 214 " constructed from a JSScript, currently connsisting of \n" 215 " filename:line:col.\n" 216 "\n" 217 " A JSScript matches the filter string is found in the\n" 218 " signature\n" 219 "\n" 220 " SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR,\n" 221 " output goes to $MOZ_UPLOAD_DIR/spew_output* to ease usage\n" 222 " with Treeherder.\n" 223 224 ); 225 // clang-format on 226 exit(0); 227 } 228 } 229 230 AutoStructuredSpewer::AutoStructuredSpewer(JSContext* cx, SpewChannel channel, 231 JSScript* script) 232 : printer_(mozilla::Nothing()) { 233 if (!cx->spewer().enabled(cx, script, channel)) { 234 return; 235 } 236 237 if (!cx->spewer().ensureInitializationAttempted()) { 238 return; 239 } 240 241 cx->spewer().startObject(cx, script, channel); 242 printer_.emplace(&cx->spewer().json_.ref()); 243 } 244 245 AutoSpewChannel::AutoSpewChannel(JSContext* cx, SpewChannel channel, 246 JSScript* script) 247 : cx_(cx) { 248 if (!cx->spewer().enabled(cx, script, channel)) { 249 wasChannelAutoSet = cx->spewer().selectedChannel_.enableChannel(channel); 250 } 251 } 252 253 AutoSpewChannel::~AutoSpewChannel() { 254 if (wasChannelAutoSet) { 255 cx_->spewer().selectedChannel_.disableAllChannels(); 256 } 257 } 258 259 #endif