summaryrefslogtreecommitdiffstats
path: root/js/src/util/StructuredSpewer.h
blob: 407dc34a25af3daf5c2cf7311fa211a8ed6f51e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/* -*- 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 "js/Printer.h"
#  include "vm/JSONPrinter.h"

#  ifdef XP_WIN
#    include <process.h>
#    define getpid _getpid
#  else
#    include <unistd.h>
#  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)                   \
    _(CacheIRHealthReport)

// 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<JSONPrinter> json_;

  // Globally selected channel.
  StructuredSpewFilter selectedChannel_;

  using NameArray =
      mozilla::EnumeratedArray<SpewChannel, SpewChannel::Count, const char*>;
  // 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<JSONPrinter*> 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 */