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 
ensureInitializationAttempted()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 
tryToInitializeOutput(const char * path)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
MatchJSScript(JSScript * script,const char * pattern)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());
92 
93   // Trivial containment match.
94   char* result = strstr(signature, pattern);
95 
96   return result != nullptr;
97 }
98 
enabled(JSScript * script)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 
enabled(JSContext * cx,const JSScript * script,SpewChannel channel) const111 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
startObject(JSContext * cx,const JSScript * script,SpewChannel channel)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());
135     json.endObject();
136   }
137 }
138 
139 /* static */
spew(JSContext * cx,SpewChannel channel,const char * fmt,...)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.formatProperty("message", fmt, ap);
162   json.endObject();
163 
164   va_end(ap);
165 }
166 
167 // Currently uses the exact spew flag representation as text.
parseSpewFlags(const char * flags)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     printf(
187         "\n"
188         "usage: SPEW=option,option,... where options can be:\n"
189         "\n"
190         "  help                     Dump this help message\n"
191         "  channel                  Enable the selected channel from below, "
192         "if\n"
193         "                           more than one channel is specified, then "
194         "the\n"
195         "                           channel will be set whichever specified "
196         "filter\n"
197         "                           comes first in STRUCTURED_CHANNEL_LIST."
198         "  AtStartup                Enable spewing at browser startup instead\n"
199         "                           of when gecko profiling starts."
200         "\n"
201         " Channels: \n"
202         "\n"
203         // List Channels
204         "  BaselineICStats          Dump the IC Entry counters during Ion "
205         "analysis\n"
206         "  ScriptStats              Dump statistics collected by tracelogger "
207         "that\n"
208         "                           is aggregated by script. Requires\n"
209         "                           JS_TRACE_LOGGING=1\n"
210         "  CacheIRHealthReport      Dump the CacheIR information and "
211         "associated "
212         "rating\n"
213         // End Channel list
214         "\n\n"
215         "By default output goes to a file called spew_output.$PID.$THREAD\n"
216         "\n"
217         "Further control of the spewer can be accomplished with the below\n"
218         "environment variables:\n"
219         "\n"
220         "   SPEW_FILE: Selects the file to write to. An absolute path.\n"
221         "\n"
222         "   SPEW_FILTER: A string which is matched against 'signature'\n"
223         "        constructed from a JSScript, currently connsisting of \n"
224         "        filename:line:col.\n"
225         "\n"
226         "        A JSScript matches the filter string is found in the\n"
227         "        signature\n"
228         "\n"
229         "   SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR,\n"
230         "        output goes to $MOZ_UPLOAD_DIR/spew_output* to ease usage\n"
231         "        with Treeherder.\n"
232 
233     );
234     exit(0);
235   }
236 }
237 
AutoStructuredSpewer(JSContext * cx,SpewChannel channel,JSScript * script)238 AutoStructuredSpewer::AutoStructuredSpewer(JSContext* cx, SpewChannel channel,
239                                            JSScript* script)
240     : printer_(mozilla::Nothing()) {
241   if (!cx->spewer().enabled(cx, script, channel)) {
242     return;
243   }
244 
245   if (!cx->spewer().ensureInitializationAttempted()) {
246     return;
247   }
248 
249   cx->spewer().startObject(cx, script, channel);
250   printer_.emplace(&cx->spewer().json_.ref());
251 }
252 
AutoSpewChannel(JSContext * cx,SpewChannel channel,JSScript * script)253 AutoSpewChannel::AutoSpewChannel(JSContext* cx, SpewChannel channel,
254                                  JSScript* script)
255     : cx_(cx) {
256   if (!cx->spewer().enabled(cx, script, channel)) {
257     wasChannelAutoSet = cx->spewer().selectedChannel_.enableChannel(channel);
258   }
259 }
260 
~AutoSpewChannel()261 AutoSpewChannel::~AutoSpewChannel() {
262   if (wasChannelAutoSet) {
263     cx_->spewer().selectedChannel_.disableAllChannels();
264   }
265 }
266 
267 #endif
268