tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */