/src/hermes/lib/VM/Profiler/SamplingProfilerSampler.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #ifndef HERMES_VM_PROFILER_GLOBALPROFILER_H |
9 | | #define HERMES_VM_PROFILER_GLOBALPROFILER_H |
10 | | |
11 | | #include "hermes/VM/Profiler/SamplingProfiler.h" |
12 | | |
13 | | #if HERMESVM_SAMPLING_PROFILER_AVAILABLE |
14 | | |
15 | | #include <condition_variable> |
16 | | #include <mutex> |
17 | | #include <thread> |
18 | | #include <unordered_set> |
19 | | |
20 | | namespace hermes { |
21 | | namespace vm { |
22 | | namespace sampling_profiler { |
23 | | /// Sampler manages the SamplingProfiler's sampling thread, and abstracts |
24 | | /// away platform-specific code for suspending the VM thread and performing the |
25 | | /// JS stack walk. |
26 | | /// |
27 | | /// Each suported platform (e.g., Posix), must have its own "flavor" of the |
28 | | /// Sampler (e.g., SamplerPosix) which must implement the |
29 | | /// Sampler::platform<<*>> methods. |
30 | | /// |
31 | | /// Usually, the platform-agnostic Sampler invokes the platform-specific |
32 | | /// ones. For example, Sampler::enable() performs some checks, then call |
33 | | /// Sampler::platformEnable() for completing the initialization. |
34 | | /// |
35 | | /// Sampling happens on a separate thread whose lifetime is managed by the |
36 | | /// Sampler's platform-specific code. The sampling thread runs the |
37 | | /// Sampler::timerLoop function, which will periodically traverse the |
38 | | /// list of registered runtimes, and perform the stack walk, which is |
39 | | /// accomplished as follows. |
40 | | /// |
41 | | /// Sampler::timerLoop invokes Sampler::sampleStacks, which will |
42 | | /// invoke Sampler::sampleStack for each registered runtime. |
43 | | /// |
44 | | /// Sampler::sampleStack invoke the platform-specific |
45 | | /// Sampler::suspendVMAndWalkStack method. This hook should be |
46 | | /// implemented on every supported platform, and it is responsible for |
47 | | /// suspending VM execution. With the stopped VM, the platform-specific code |
48 | | /// calls back into platform-agnostic code (Sampler::walkRuntimeStack) so |
49 | | /// stack walking can continue. |
50 | | /// |
51 | | /// When Sampler::walkRuntimeStack runs the VM is suspended, but this |
52 | | /// suspension can happen when the VM is in the middle of, e.g., memory |
53 | | /// allocation, or while it is holding some lock. Thus, |
54 | | /// Sampler::walkRuntimeStack should not acquire locks, or even allocate |
55 | | /// memory. It should perform stack walking quickly and expeditiously. All |
56 | | /// buffers used for stack walking are pre-allocated before calling into this |
57 | | /// function. |
58 | | /// |
59 | | /// Finally, when Sampler::walkRuntimeStack returns to |
60 | | /// Sampler::suspendVMAndWalkStack, VM execution should be resumed. |
61 | | struct Sampler { |
62 | | virtual ~Sampler(); |
63 | | |
64 | | /// Lock for profiler operations and access to member fields. |
65 | | std::mutex profilerLock_; |
66 | | |
67 | | /// Profiler instances for all the individual runtimes that are currently |
68 | | /// registered. |
69 | | std::unordered_set<SamplingProfiler *> profilers_; |
70 | | |
71 | | /// Whether profiler is enabled or not. Protected by profilerLock_. |
72 | | bool enabled_{false}; |
73 | | |
74 | | /// Threading: load/store of sampledStackDepth_ and sampleStorage_ |
75 | | /// are protected by samplingDoneSem_. |
76 | | /// Actual sampled stack depth in sampleStorage_. |
77 | | uint32_t sampledStackDepth_{0}; |
78 | | /// Preallocated stack frames storage for signal handler(because |
79 | | /// allocating memory in signal handler is not allowed) |
80 | | /// This storage does not need to be protected by lock because accessing to |
81 | | /// it is serialized by samplingDoneSem_. |
82 | | SamplingProfiler::StackTrace sampleStorage_{SamplingProfiler::kMaxStackDepth}; |
83 | | |
84 | | /// This thread starts in timerLoop_, and samples the stacks of registered |
85 | | /// runtimes periodically. It is created in \p enable() and joined in |
86 | | /// \p disable(). |
87 | | std::thread timerThread_; |
88 | | |
89 | | /// This condition variable can be used to wait for a change in the enabled |
90 | | /// member variable. |
91 | | std::condition_variable enabledCondVar_; |
92 | | |
93 | | /// Main routine to take a sample of runtime stack. |
94 | | /// \return false for failure which timer loop thread should stop. |
95 | | bool sampleStacks(); |
96 | | |
97 | | /// Timer loop thread main routine. |
98 | | void timerLoop(double meanHzFreq); |
99 | | |
100 | | /// Implementation of SamplingProfiler::enable/disable. |
101 | | bool enable(double meanHzFreq); |
102 | | bool disable(); |
103 | | |
104 | | /// \return true if the sampling profiler is enabled, false otherwise. |
105 | | bool enabled(); |
106 | | |
107 | | /// Register the \p profiler associated with an active runtime. |
108 | | /// Should only be called from the thread running the hermes runtime |
109 | | /// associated with \p profiler. |
110 | | void registerRuntime(SamplingProfiler *profiler); |
111 | | |
112 | | /// Unregister the active runtime and current thread associated with |
113 | | /// \p profiler. |
114 | | void unregisterRuntime(SamplingProfiler *profiler); |
115 | | |
116 | | /// \return the singleton profiler instance. |
117 | | static Sampler *get(); |
118 | | |
119 | | protected: |
120 | 0 | Sampler() = default; |
121 | | |
122 | | void walkRuntimeStack(SamplingProfiler *profiler); |
123 | | |
124 | | private: |
125 | | /// Sample stack for a profiler. |
126 | | bool sampleStack(SamplingProfiler *localProfiler); |
127 | | |
128 | | // Platform-specific hooks. |
129 | | |
130 | | /// Platform-specific hook invoked while enabling the sampling profiler. This |
131 | | /// hook is invoked prior to creating the sampling thread, and with |
132 | | /// this->profilerLock_ lock held. It must \return true if the sampling |
133 | | /// profiler can be enabled, and false otherwise. |
134 | | bool platformEnable(); |
135 | | |
136 | | /// Platform-specific hook invoked while disabling the sampling profiler. |
137 | | /// This hook is invoked prior to terminating the sampling thread, and with |
138 | | /// this->profilerLock_ lock held. It must \return true if the sampling |
139 | | /// profiler can be disabled, and false otherwise. |
140 | | bool platformDisable(); |
141 | | |
142 | | /// Platform-specific hook invoked after \p profiler is registered for |
143 | | /// sampling profiling. It is invoked with this->profilerLock_ lock held. |
144 | | void platformRegisterRuntime(SamplingProfiler *profiler); |
145 | | |
146 | | /// Platform-specific hook invoked after \p profiler is removed from the list |
147 | | /// of known profilers -- i.e., profiling should stop on \p profiler. It is |
148 | | /// invoked with this->profilerLock_ lock held. |
149 | | void platformUnregisterRuntime(SamplingProfiler *profiler); |
150 | | |
151 | | /// Platform-specific hook invoked after sampleStack() collects the current |
152 | | /// stack of \p localProfiler. This is invoked with the |
153 | | /// \p localProfiler->runtimeDataLock_ lock held outside of the VM thread. |
154 | | void platformPostSampleStack(SamplingProfiler *localProfiler); |
155 | | |
156 | | /// Platform-specific hook invoked to suspend the VM thread and perform stack |
157 | | /// sampling. Note that this method is invoked with both this->profilerLock_ |
158 | | /// and \p profiler->runtimeDataLock_ locks held. |
159 | | bool platformSuspendVMAndWalkStack(SamplingProfiler *profiler); |
160 | | }; |
161 | | } // namespace sampling_profiler |
162 | | } // namespace vm |
163 | | } // namespace hermes |
164 | | |
165 | | #endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE |
166 | | |
167 | | #endif // HERMES_VM_PROFILER_GLOBALPROFILER_H |