tor-browser

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

GeckoProfiler.h (12413B)


      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 vm_GeckoProfiler_h
      8 #define vm_GeckoProfiler_h
      9 
     10 #include "mozilla/Attributes.h"
     11 #include "mozilla/BaseProfilerMarkersPrerequisites.h"
     12 #include "mozilla/DebugOnly.h"
     13 #include "mozilla/TimeStamp.h"
     14 
     15 #include <stddef.h>
     16 #include <stdint.h>
     17 
     18 #include "jspubtd.h"
     19 
     20 #include "js/AllocPolicy.h"
     21 #include "js/HashTable.h"
     22 #include "js/ProfilingCategory.h"
     23 #include "js/ProfilingSources.h"
     24 #include "js/TypeDecls.h"
     25 #include "js/Utility.h"
     26 #include "threading/ExclusiveData.h"
     27 #include "threading/ProtectedData.h"
     28 
     29 /*
     30 * Gecko Profiler integration with the JS Engine
     31 * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
     32 *
     33 * The Gecko Profiler (found in tools/profiler) is an implementation of a
     34 * profiler which has the ability to walk the C++ stack as well as use
     35 * instrumentation to gather information. When dealing with JS, however, the
     36 * profiler needs integration with the engine because otherwise it is very
     37 * difficult to figure out what javascript is executing.
     38 *
     39 * The current method of integration with the profiler is a form of
     40 * instrumentation: every time a JS function is entered, a bit of information
     41 * is pushed onto a stack that the profiler owns and maintains. This
     42 * information is then popped at the end of the JS function. The profiler
     43 * informs the JS engine of this stack at runtime, and it can by turned on/off
     44 * dynamically. Each stack frame has type ProfilingStackFrame.
     45 *
     46 * Throughout execution, the size of the stack recorded in memory may exceed the
     47 * maximum. The JS engine will not write any information past the maximum limit,
     48 * but it will still maintain the size of the stack. Profiler code is aware of
     49 * this and iterates the stack accordingly.
     50 *
     51 * There is some information pushed on the profiler stack for every JS function
     52 * that is entered. First is a char* label with a description of what function
     53 * was entered. Currently this string is of the form "function (file:line)" if
     54 * there's a function name, or just "file:line" if there's no function name
     55 * available. The other bit of information is the relevant C++ (native) stack
     56 * pointer. This stack pointer is what enables the interleaving of the C++ and
     57 * the JS stack. Finally, throughout execution of the function, some extra
     58 * information may be updated on the ProfilingStackFrame structure.
     59 *
     60 * = Profile Strings
     61 *
     62 * The profile strings' allocations and deallocation must be carefully
     63 * maintained, and ideally at a very low overhead cost. For this reason, the JS
     64 * engine maintains a mapping of all known profile strings. These strings are
     65 * keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
     66 * JSScript* pair. A JSScript will destroy its corresponding profile string when
     67 * the script is finalized.
     68 *
     69 * For this reason, a char* pointer pushed on the profiler stack is valid only
     70 * while it is on the profiler stack. The profiler uses sampling to read off
     71 * information from this instrumented stack, and it therefore copies the string
     72 * byte for byte when a JS function is encountered during sampling.
     73 *
     74 * = Native Stack Pointer
     75 *
     76 * The actual value pushed as the native pointer is nullptr for most JS
     77 * functions. The reason for this is that there's actually very little
     78 * correlation between the JS stack and the C++ stack because many JS functions
     79 * all run in the same C++ frame, or can even go backwards in C++ when going
     80 * from the JIT back to the interpreter.
     81 *
     82 * To alleviate this problem, all JS functions push nullptr as their "native
     83 * stack pointer" to indicate that it's a JS function call. The function
     84 * RunScript(), however, pushes an actual C++ stack pointer onto the profiler
     85 * stack. This way when interleaving C++ and JS, if the Gecko Profiler sees a
     86 * nullptr native stack pointer on the profiler stack, it looks backwards for
     87 * the first non-nullptr pointer and uses that for all subsequent nullptr
     88 * native stack pointers.
     89 *
     90 * = Line Numbers
     91 *
     92 * One goal of sampling is to get both a backtrace of the JS stack, but also
     93 * know where within each function on the stack execution currently is. For
     94 * this, each ProfilingStackFrame has a 'pc' field to tell where its execution
     95 * currently is. This field is updated whenever a call is made to another JS
     96 * function, and for the JIT it is also updated whenever the JIT is left.
     97 *
     98 * This field is in a union with a uint32_t 'line' so that C++ can make use of
     99 * the field as well. It was observed that tracking 'line' via PCToLineNumber in
    100 * JS was far too expensive, so that is why the pc instead of the translated
    101 * line number is stored.
    102 *
    103 * As an invariant, if the pc is nullptr, then the JIT is currently executing
    104 * generated code. Otherwise execution is in another JS function or in C++. With
    105 * this in place, only the top frame of the stack can ever have nullptr as its
    106 * pc. Additionally with this invariant, it is possible to maintain mappings of
    107 * JIT code to pc which can be accessed safely because they will only be
    108 * accessed from a signal handler when the JIT code is executing.
    109 */
    110 
    111 class JS_PUBLIC_API ProfilingStack;
    112 
    113 namespace js {
    114 
    115 class BaseScript;
    116 class GeckoProfilerThread;
    117 class ScriptSource;
    118 
    119 // The `ProfileStringMap` weakly holds its `BaseScript*` keys and owns its
    120 // string values. Entries are removed when the `BaseScript` is finalized; see
    121 // `GeckoProfiler::onScriptFinalized`.
    122 using ProfileStringMap = HashMap<BaseScript*, JS::UniqueChars,
    123                                 DefaultHasher<BaseScript*>, SystemAllocPolicy>;
    124 
    125 using ProfilerScriptSourceSet =
    126    HashSet<RefPtr<ScriptSource>, PointerHasher<ScriptSource*>,
    127            SystemAllocPolicy>;
    128 
    129 class GeckoProfilerRuntime {
    130  JSRuntime* rt;
    131  MainThreadData<ProfileStringMap> strings_;
    132  RWExclusiveData<ProfilerScriptSourceSet> scriptSources_;
    133  bool slowAssertions;
    134  uint32_t enabled_;
    135  void (*eventMarker_)(mozilla::MarkerCategory, const char*, const char*);
    136  void (*intervalMarker_)(mozilla::MarkerCategory, const char*,
    137                          mozilla::TimeStamp, const char*);
    138  void (*flowMarker_)(mozilla::MarkerCategory, const char*, uint64_t);
    139  void (*terminatingFlowMarker_)(mozilla::MarkerCategory, const char*,
    140                                 uint64_t);
    141 
    142 public:
    143  explicit GeckoProfilerRuntime(JSRuntime* rt);
    144 
    145  /* management of whether instrumentation is on or off */
    146  bool enabled() const { return enabled_; }
    147  void enable(bool enabled);
    148  void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
    149  bool slowAssertionsEnabled() { return slowAssertions; }
    150 
    151  void setEventMarker(void (*fn)(mozilla::MarkerCategory, const char*,
    152                                 const char*));
    153  void setIntervalMarker(void (*fn)(mozilla::MarkerCategory, const char*,
    154                                    mozilla::TimeStamp, const char*));
    155  void setFlowMarker(void (*fn)(mozilla::MarkerCategory, const char*,
    156                                uint64_t));
    157  void setTerminatingFlowMarker(void (*fn)(mozilla::MarkerCategory, const char*,
    158                                           uint64_t));
    159 
    160  static JS::UniqueChars allocProfileString(JSContext* cx, BaseScript* script);
    161  const char* profileString(JSContext* cx, BaseScript* script);
    162 
    163  void onScriptFinalized(BaseScript* script);
    164 
    165  void markEvent(
    166      const char* event, const char* details,
    167      JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
    168 
    169  void markInterval(
    170      const char* event, mozilla::TimeStamp start, const char* details,
    171      JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
    172 
    173  // Note that flowId will be added as a process-scoped id for both
    174  // markFlow and markTerminatingFlow.
    175  //
    176  // See baseprofiler/public/Flow.h
    177  void markFlow(
    178      const char* markerName, uint64_t flowId,
    179      JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
    180  void markTerminatingFlow(
    181      const char* markerName, uint64_t flowId,
    182      JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
    183 
    184  ProfileStringMap& strings() { return strings_.ref(); }
    185 
    186  /* meant to be used for testing, not recommended to call in normal code */
    187  size_t stringsCount();
    188  void stringsReset();
    189 
    190  bool insertScriptSource(ScriptSource* scriptSource) {
    191    MOZ_ASSERT(scriptSource);
    192    auto guard = scriptSources_.writeLock();
    193    if (!enabled_) {
    194      return true;
    195    }
    196 
    197    return guard->put(scriptSource);
    198  }
    199 
    200  js::ProfilerJSSources getProfilerScriptSources();
    201 
    202  size_t scriptSourcesCount() { return scriptSources_.readLock()->count(); }
    203 
    204  const uint32_t* addressOfEnabled() const { return &enabled_; }
    205 
    206  void fixupStringsMapAfterMovingGC();
    207 #ifdef JSGC_HASH_TABLE_CHECKS
    208  void checkStringsMapAfterMovingGC();
    209 #endif
    210 };
    211 
    212 inline size_t GeckoProfilerRuntime::stringsCount() { return strings().count(); }
    213 
    214 inline void GeckoProfilerRuntime::stringsReset() { strings().clear(); }
    215 
    216 /*
    217 * This class is used in RunScript() to push the marker onto the sampling stack
    218 * that we're about to enter JS function calls. This is the only time in which a
    219 * valid stack pointer is pushed to the sampling stack.
    220 */
    221 class MOZ_RAII GeckoProfilerEntryMarker {
    222 public:
    223  explicit MOZ_ALWAYS_INLINE GeckoProfilerEntryMarker(JSContext* cx,
    224                                                      JSScript* script);
    225  MOZ_ALWAYS_INLINE ~GeckoProfilerEntryMarker();
    226 
    227 private:
    228  GeckoProfilerThread* profiler_;
    229 #ifdef DEBUG
    230  uint32_t spBefore_;
    231 #endif
    232 };
    233 
    234 /*
    235 * RAII class to automatically add Gecko Profiler profiling stack frames.
    236 * It retrieves the ProfilingStack from the JSContext and does nothing if the
    237 * profiler is inactive.
    238 *
    239 * NB: The `label` string must be statically allocated.
    240 */
    241 class MOZ_RAII AutoGeckoProfilerEntry {
    242 public:
    243  explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
    244      JSContext* cx, const char* label, const char* dynamicString,
    245      JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
    246      uint32_t flags = 0);
    247  explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
    248      JSContext* cx, const char* label,
    249      JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
    250      uint32_t flags = 0);
    251  MOZ_ALWAYS_INLINE ~AutoGeckoProfilerEntry();
    252 
    253 private:
    254  ProfilingStack* profilingStack_;
    255 #ifdef DEBUG
    256  GeckoProfilerThread* profiler_;
    257  uint32_t spBefore_;
    258 #endif
    259 };
    260 
    261 /*
    262 * Use this RAII class to add Gecko Profiler label frames for methods of the
    263 * JavaScript builtin API.
    264 * These frames will be exposed to JavaScript developers (ie they won't be
    265 * filtered out when using the "JavaScript" filtering option in the Firefox
    266 * Profiler UI).
    267 * Technical note: the label and dynamicString values will be joined with a dot
    268 * separator if dynamicString is present.
    269 */
    270 class MOZ_RAII AutoJSMethodProfilerEntry : public AutoGeckoProfilerEntry {
    271 public:
    272  explicit MOZ_ALWAYS_INLINE AutoJSMethodProfilerEntry(
    273      JSContext* cx, const char* label, const char* dynamicString = nullptr);
    274 };
    275 
    276 /*
    277 * Use this RAII class to add Gecko Profiler label frames for constructors of
    278 * the JavaScript builtin API.
    279 * These frames will be exposed to JavaScript developers (ie they won't be
    280 * filtered out when using the "JavaScript" filtering option in the Firefox
    281 * Profiler UI).
    282 * Technical note: the word "constructor" will be appended to the label (with a
    283 * space separator).
    284 */
    285 class MOZ_RAII AutoJSConstructorProfilerEntry : public AutoGeckoProfilerEntry {
    286 public:
    287  explicit MOZ_ALWAYS_INLINE AutoJSConstructorProfilerEntry(JSContext* cx,
    288                                                            const char* label);
    289 };
    290 
    291 /*
    292 * This class is used in the interpreter to bound regions where the baseline JIT
    293 * being entered via OSR.  It marks the current top profiling stack frame as
    294 * OSR-ed
    295 */
    296 class MOZ_RAII GeckoProfilerBaselineOSRMarker {
    297 public:
    298  explicit GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame);
    299  ~GeckoProfilerBaselineOSRMarker();
    300 
    301 private:
    302  GeckoProfilerThread* profiler;
    303  mozilla::DebugOnly<uint32_t> spBefore_;
    304 };
    305 
    306 } /* namespace js */
    307 
    308 #endif /* vm_GeckoProfiler_h */