1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #include "common.h" |
6 | #include "eventpipebuffermanager.h" |
7 | #include "eventpipeeventinstance.h" |
8 | #include "sampleprofiler.h" |
9 | #include "hosting.h" |
10 | #include "threadsuspend.h" |
11 | |
12 | #ifdef FEATURE_PERFTRACING |
13 | |
14 | #ifndef FEATURE_PAL |
15 | #include <mmsystem.h> |
16 | #endif //FEATURE_PAL |
17 | |
18 | #define NUM_NANOSECONDS_IN_1_MS (1000000) |
19 | |
20 | Volatile<BOOL> SampleProfiler::s_profilingEnabled = false; |
21 | Thread* SampleProfiler::s_pSamplingThread = NULL; |
22 | const WCHAR* SampleProfiler::s_providerName = W("Microsoft-DotNETCore-SampleProfiler" ); |
23 | EventPipeProvider* SampleProfiler::s_pEventPipeProvider = NULL; |
24 | EventPipeEvent* SampleProfiler::s_pThreadTimeEvent = NULL; |
25 | BYTE* SampleProfiler::s_pPayloadExternal = NULL; |
26 | BYTE* SampleProfiler::s_pPayloadManaged = NULL; |
27 | CLREventStatic SampleProfiler::s_threadShutdownEvent; |
28 | unsigned long SampleProfiler::s_samplingRateInNs = NUM_NANOSECONDS_IN_1_MS; // 1ms |
29 | bool SampleProfiler::s_timePeriodIsSet = FALSE; |
30 | |
31 | #ifndef FEATURE_PAL |
32 | PVOID SampleProfiler::s_timeBeginPeriodFn = NULL; |
33 | PVOID SampleProfiler::s_timeEndPeriodFn = NULL; |
34 | HINSTANCE SampleProfiler::s_hMultimediaLib = NULL; |
35 | |
36 | typedef MMRESULT (WINAPI *TimePeriodFnPtr) (UINT uPeriod); |
37 | #endif //FEATURE_PAL |
38 | |
39 | void SampleProfiler::Enable() |
40 | { |
41 | CONTRACTL |
42 | { |
43 | THROWS; |
44 | GC_TRIGGERS; |
45 | MODE_ANY; |
46 | PRECONDITION(s_pSamplingThread == NULL); |
47 | // Synchronization of multiple callers occurs in EventPipe::Enable. |
48 | PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); |
49 | } |
50 | CONTRACTL_END; |
51 | |
52 | LoadDependencies(); |
53 | |
54 | if(s_pEventPipeProvider == NULL) |
55 | { |
56 | s_pEventPipeProvider = EventPipe::CreateProvider(SL(s_providerName)); |
57 | s_pThreadTimeEvent = s_pEventPipeProvider->AddEvent( |
58 | 0, /* eventID */ |
59 | 0, /* keywords */ |
60 | 0, /* eventVersion */ |
61 | EventPipeEventLevel::Informational, |
62 | false /* NeedStack */); |
63 | } |
64 | |
65 | // Check to see if the sample profiler event is enabled. If it is not, do not spin up the sampling thread. |
66 | if(!s_pThreadTimeEvent->IsEnabled()) |
67 | { |
68 | return; |
69 | } |
70 | |
71 | if(s_pPayloadExternal == NULL) |
72 | { |
73 | s_pPayloadExternal = new BYTE[sizeof(unsigned int)]; |
74 | *((unsigned int *)s_pPayloadExternal) = static_cast<unsigned int>(SampleProfilerSampleType::External); |
75 | |
76 | s_pPayloadManaged = new BYTE[sizeof(unsigned int)]; |
77 | *((unsigned int *)s_pPayloadManaged) = static_cast<unsigned int>(SampleProfilerSampleType::Managed); |
78 | } |
79 | |
80 | s_profilingEnabled = true; |
81 | s_pSamplingThread = SetupUnstartedThread(); |
82 | if(s_pSamplingThread->CreateNewThread(0, ThreadProc, NULL)) |
83 | { |
84 | // Start the sampling thread. |
85 | s_pSamplingThread->SetBackground(TRUE); |
86 | s_pSamplingThread->StartThread(); |
87 | } |
88 | else |
89 | { |
90 | _ASSERT(!"Unable to create sample profiler thread." ); |
91 | } |
92 | |
93 | s_threadShutdownEvent.CreateManualEvent(FALSE); |
94 | |
95 | SetTimeGranularity(); |
96 | } |
97 | |
98 | void SampleProfiler::Disable() |
99 | { |
100 | CONTRACTL |
101 | { |
102 | THROWS; |
103 | GC_TRIGGERS; |
104 | MODE_ANY; |
105 | // Synchronization of multiple callers occurs in EventPipe::Disable. |
106 | PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); |
107 | } |
108 | CONTRACTL_END; |
109 | |
110 | // Bail early if profiling is not enabled. |
111 | if(!s_profilingEnabled) |
112 | { |
113 | return; |
114 | } |
115 | |
116 | // Reset the event before shutdown. |
117 | s_threadShutdownEvent.Reset(); |
118 | |
119 | // The sampling thread will watch this value and exit |
120 | // when profiling is disabled. |
121 | s_profilingEnabled = false; |
122 | |
123 | // Wait for the sampling thread to clean itself up. |
124 | s_threadShutdownEvent.Wait(0, FALSE /* bAlertable */); |
125 | |
126 | if(s_timePeriodIsSet) |
127 | { |
128 | ResetTimeGranularity(); |
129 | } |
130 | UnloadDependencies(); |
131 | } |
132 | |
133 | void SampleProfiler::SetSamplingRate(unsigned long nanoseconds) |
134 | { |
135 | LIMITED_METHOD_CONTRACT; |
136 | |
137 | // If the time period setting was modified by us, |
138 | // make sure to change it back before changing our period |
139 | // and losing track of what we set it to |
140 | if(s_timePeriodIsSet) |
141 | { |
142 | ResetTimeGranularity(); |
143 | } |
144 | |
145 | s_samplingRateInNs = nanoseconds; |
146 | |
147 | if(!s_timePeriodIsSet) |
148 | { |
149 | SetTimeGranularity(); |
150 | } |
151 | } |
152 | |
153 | DWORD WINAPI SampleProfiler::ThreadProc(void *args) |
154 | { |
155 | CONTRACTL |
156 | { |
157 | NOTHROW; |
158 | GC_TRIGGERS; |
159 | MODE_PREEMPTIVE; |
160 | PRECONDITION(s_pSamplingThread != NULL); |
161 | } |
162 | CONTRACTL_END; |
163 | |
164 | // Complete thread initialization and start the profiling loop. |
165 | if(s_pSamplingThread->HasStarted()) |
166 | { |
167 | // Switch to pre-emptive mode so that this thread doesn't starve the GC. |
168 | GCX_PREEMP(); |
169 | |
170 | while(s_profilingEnabled) |
171 | { |
172 | // Check to see if we can suspend managed execution. |
173 | if(ThreadSuspend::SysIsSuspendInProgress() || (ThreadSuspend::GetSuspensionThread() != 0)) |
174 | { |
175 | // Skip the current sample. |
176 | PlatformSleep(s_samplingRateInNs); |
177 | continue; |
178 | } |
179 | |
180 | // Actually suspend managed execution. |
181 | ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_REASON::SUSPEND_OTHER); |
182 | |
183 | // Walk all managed threads and capture stacks. |
184 | WalkManagedThreads(); |
185 | |
186 | // Resume managed execution. |
187 | ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */); |
188 | |
189 | // Wait until it's time to sample again. |
190 | PlatformSleep(s_samplingRateInNs); |
191 | } |
192 | } |
193 | |
194 | // Destroy the sampling thread when it is done running. |
195 | DestroyThread(s_pSamplingThread); |
196 | s_pSamplingThread = NULL; |
197 | |
198 | // Signal Disable() that the thread has been destroyed. |
199 | s_threadShutdownEvent.Set(); |
200 | |
201 | return S_OK; |
202 | } |
203 | |
204 | // The thread store lock must already be held by the thread before this function |
205 | // is called. ThreadSuspend::SuspendEE acquires the thread store lock. |
206 | void SampleProfiler::WalkManagedThreads() |
207 | { |
208 | CONTRACTL |
209 | { |
210 | NOTHROW; |
211 | GC_TRIGGERS; |
212 | MODE_PREEMPTIVE; |
213 | } |
214 | CONTRACTL_END; |
215 | |
216 | Thread *pTargetThread = NULL; |
217 | |
218 | // Iterate over all managed threads. |
219 | // Assumes that the ThreadStoreLock is held because we've suspended all threads. |
220 | while ((pTargetThread = ThreadStore::GetThreadList(pTargetThread)) != NULL) |
221 | { |
222 | StackContents stackContents; |
223 | |
224 | // Walk the stack and write it out as an event. |
225 | if(EventPipe::WalkManagedStackForThread(pTargetThread, stackContents) && !stackContents.IsEmpty()) |
226 | { |
227 | // Set the payload. If the GC mode on suspension > 0, then the thread was in cooperative mode. |
228 | // Even though there are some cases where this is not managed code, we assume it is managed code here. |
229 | // If the GC mode on suspension == 0 then the thread was in preemptive mode, which we qualify as external here. |
230 | BYTE *pPayload = s_pPayloadExternal; |
231 | if(pTargetThread->GetGCModeOnSuspension()) |
232 | { |
233 | pPayload = s_pPayloadManaged; |
234 | } |
235 | |
236 | // Write the sample. |
237 | EventPipe::WriteSampleProfileEvent(s_pSamplingThread, s_pThreadTimeEvent, pTargetThread, stackContents, pPayload, c_payloadSize); |
238 | } |
239 | |
240 | // Reset the GC mode. |
241 | pTargetThread->ClearGCModeOnSuspension(); |
242 | } |
243 | } |
244 | |
245 | void SampleProfiler::PlatformSleep(unsigned long nanoseconds) |
246 | { |
247 | CONTRACTL |
248 | { |
249 | NOTHROW; |
250 | GC_TRIGGERS; |
251 | MODE_ANY; |
252 | } |
253 | CONTRACTL_END; |
254 | |
255 | #ifdef FEATURE_PAL |
256 | PAL_nanosleep(nanoseconds); |
257 | #else //FEATURE_PAL |
258 | ClrSleepEx(s_samplingRateInNs / NUM_NANOSECONDS_IN_1_MS, FALSE); |
259 | #endif //FEATURE_PAL |
260 | } |
261 | |
262 | void SampleProfiler::SetTimeGranularity() |
263 | { |
264 | CONTRACTL |
265 | { |
266 | NOTHROW; |
267 | GC_NOTRIGGER; |
268 | MODE_ANY; |
269 | } |
270 | CONTRACTL_END; |
271 | |
272 | #ifndef FEATURE_PAL |
273 | // Attempt to set the systems minimum timer period to the sampling rate |
274 | // If the sampling rate is lower than the current system setting (16ms by default), |
275 | // this will cause the OS to wake more often for scheduling descsion, allowing us to take samples |
276 | // Note that is effects a system-wide setting and when set low will increase the amount of time |
277 | // the OS is on-CPU, decreasing overall system performance and increasing power consumption |
278 | if(s_timeBeginPeriodFn != NULL) |
279 | { |
280 | if(((TimePeriodFnPtr) s_timeBeginPeriodFn)(s_samplingRateInNs / NUM_NANOSECONDS_IN_1_MS) == TIMERR_NOERROR) |
281 | { |
282 | s_timePeriodIsSet = TRUE; |
283 | } |
284 | } |
285 | #endif //FEATURE_PAL |
286 | } |
287 | |
288 | void SampleProfiler::ResetTimeGranularity() |
289 | { |
290 | CONTRACTL |
291 | { |
292 | NOTHROW; |
293 | GC_NOTRIGGER; |
294 | MODE_ANY; |
295 | } |
296 | CONTRACTL_END; |
297 | |
298 | #ifndef FEATURE_PAL |
299 | // End the modifications we had to the timer period in Enable |
300 | if(s_timeEndPeriodFn != NULL) |
301 | { |
302 | if(((TimePeriodFnPtr) s_timeEndPeriodFn)(s_samplingRateInNs / NUM_NANOSECONDS_IN_1_MS) == TIMERR_NOERROR) |
303 | { |
304 | s_timePeriodIsSet = FALSE; |
305 | } |
306 | } |
307 | #endif //FEATURE_PAL |
308 | } |
309 | |
310 | bool SampleProfiler::LoadDependencies() |
311 | { |
312 | CONTRACTL |
313 | { |
314 | NOTHROW; |
315 | GC_NOTRIGGER; |
316 | MODE_ANY; |
317 | } |
318 | CONTRACTL_END; |
319 | |
320 | #ifndef FEATURE_PAL |
321 | s_hMultimediaLib = WszLoadLibrary(W("winmm.dll" )); |
322 | |
323 | if (s_hMultimediaLib != NULL) |
324 | { |
325 | s_timeBeginPeriodFn = (PVOID) GetProcAddress(s_hMultimediaLib, "timeBeginPeriod" ); |
326 | s_timeEndPeriodFn = (PVOID) GetProcAddress(s_hMultimediaLib, "timeEndPeriod" ); |
327 | } |
328 | |
329 | return s_hMultimediaLib != NULL && s_timeBeginPeriodFn != NULL && s_timeEndPeriodFn != NULL; |
330 | #else |
331 | return FALSE; |
332 | #endif //FEATURE_PAL |
333 | } |
334 | |
335 | void SampleProfiler::UnloadDependencies() |
336 | { |
337 | CONTRACTL |
338 | { |
339 | NOTHROW; |
340 | GC_NOTRIGGER; |
341 | MODE_ANY; |
342 | } |
343 | CONTRACTL_END; |
344 | |
345 | #ifndef FEATURE_PAL |
346 | if (s_hMultimediaLib != NULL) |
347 | { |
348 | FreeLibrary(s_hMultimediaLib); |
349 | s_hMultimediaLib = NULL; |
350 | s_timeBeginPeriodFn = NULL; |
351 | s_timeEndPeriodFn = NULL; |
352 | } |
353 | #endif //FEATURE_PAL |
354 | } |
355 | |
356 | #endif // FEATURE_PERFTRACING |
357 | |