tor-browser

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

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