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