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
20Volatile<BOOL> SampleProfiler::s_profilingEnabled = false;
21Thread* SampleProfiler::s_pSamplingThread = NULL;
22const WCHAR* SampleProfiler::s_providerName = W("Microsoft-DotNETCore-SampleProfiler");
23EventPipeProvider* SampleProfiler::s_pEventPipeProvider = NULL;
24EventPipeEvent* SampleProfiler::s_pThreadTimeEvent = NULL;
25BYTE* SampleProfiler::s_pPayloadExternal = NULL;
26BYTE* SampleProfiler::s_pPayloadManaged = NULL;
27CLREventStatic SampleProfiler::s_threadShutdownEvent;
28unsigned long SampleProfiler::s_samplingRateInNs = NUM_NANOSECONDS_IN_1_MS; // 1ms
29bool SampleProfiler::s_timePeriodIsSet = FALSE;
30
31#ifndef FEATURE_PAL
32PVOID SampleProfiler::s_timeBeginPeriodFn = NULL;
33PVOID SampleProfiler::s_timeEndPeriodFn = NULL;
34HINSTANCE SampleProfiler::s_hMultimediaLib = NULL;
35
36typedef MMRESULT (WINAPI *TimePeriodFnPtr) (UINT uPeriod);
37#endif //FEATURE_PAL
38
39void 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
98void 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
133void 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
153DWORD 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.
206void 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
245void 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
262void 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
288void 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
310bool 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
335void 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