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