|
1 // Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. |
|
2 // |
|
3 // Redistribution and use in source and binary forms, with or without |
|
4 // modification, are permitted provided that the following conditions |
|
5 // are met: |
|
6 // * Redistributions of source code must retain the above copyright |
|
7 // notice, this list of conditions and the following disclaimer. |
|
8 // * Redistributions in binary form must reproduce the above copyright |
|
9 // notice, this list of conditions and the following disclaimer in |
|
10 // the documentation and/or other materials provided with the |
|
11 // distribution. |
|
12 // * Neither the name of Google, Inc. nor the names of its contributors |
|
13 // may be used to endorse or promote products derived from this |
|
14 // software without specific prior written permission. |
|
15 // |
|
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
19 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|
20 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
21 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
22 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|
23 // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
25 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
|
26 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
27 // SUCH DAMAGE. |
|
28 |
|
29 #include <windows.h> |
|
30 #include <mmsystem.h> |
|
31 #include <process.h> |
|
32 #include "platform.h" |
|
33 #include "TableTicker.h" |
|
34 #include "ProfileEntry.h" |
|
35 #include "UnwinderThread2.h" |
|
36 |
|
37 class PlatformData : public Malloced { |
|
38 public: |
|
39 // Get a handle to the calling thread. This is the thread that we are |
|
40 // going to profile. We need to make a copy of the handle because we are |
|
41 // going to use it in the sampler thread. Using GetThreadHandle() will |
|
42 // not work in this case. We're using OpenThread because DuplicateHandle |
|
43 // for some reason doesn't work in Chrome's sandbox. |
|
44 PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT | |
|
45 THREAD_SUSPEND_RESUME | |
|
46 THREAD_QUERY_INFORMATION, |
|
47 false, |
|
48 aThreadId)) {} |
|
49 |
|
50 ~PlatformData() { |
|
51 if (profiled_thread_ != NULL) { |
|
52 CloseHandle(profiled_thread_); |
|
53 profiled_thread_ = NULL; |
|
54 } |
|
55 } |
|
56 |
|
57 HANDLE profiled_thread() { return profiled_thread_; } |
|
58 |
|
59 private: |
|
60 HANDLE profiled_thread_; |
|
61 }; |
|
62 |
|
63 /* static */ PlatformData* |
|
64 Sampler::AllocPlatformData(int aThreadId) |
|
65 { |
|
66 return new PlatformData(aThreadId); |
|
67 } |
|
68 |
|
69 /* static */ void |
|
70 Sampler::FreePlatformData(PlatformData* aData) |
|
71 { |
|
72 delete aData; |
|
73 } |
|
74 |
|
75 uintptr_t |
|
76 Sampler::GetThreadHandle(PlatformData* aData) |
|
77 { |
|
78 return (uintptr_t) aData->profiled_thread(); |
|
79 } |
|
80 |
|
81 class SamplerThread : public Thread { |
|
82 public: |
|
83 SamplerThread(double interval, Sampler* sampler) |
|
84 : Thread("SamplerThread") |
|
85 , interval_(interval) |
|
86 , sampler_(sampler) |
|
87 { |
|
88 interval_ = floor(interval + 0.5); |
|
89 if (interval_ <= 0) { |
|
90 interval_ = 1; |
|
91 } |
|
92 } |
|
93 |
|
94 static void StartSampler(Sampler* sampler) { |
|
95 if (instance_ == NULL) { |
|
96 instance_ = new SamplerThread(sampler->interval(), sampler); |
|
97 instance_->Start(); |
|
98 } else { |
|
99 ASSERT(instance_->interval_ == sampler->interval()); |
|
100 } |
|
101 } |
|
102 |
|
103 static void StopSampler() { |
|
104 instance_->Join(); |
|
105 delete instance_; |
|
106 instance_ = NULL; |
|
107 } |
|
108 |
|
109 // Implement Thread::Run(). |
|
110 virtual void Run() { |
|
111 |
|
112 // By default we'll not adjust the timer resolution which tends to be around |
|
113 // 16ms. However, if the requested interval is sufficiently low we'll try to |
|
114 // adjust the resolution to match. |
|
115 if (interval_ < 10) |
|
116 ::timeBeginPeriod(interval_); |
|
117 |
|
118 while (sampler_->IsActive()) { |
|
119 if (!sampler_->IsPaused()) { |
|
120 mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); |
|
121 std::vector<ThreadInfo*> threads = |
|
122 sampler_->GetRegisteredThreads(); |
|
123 for (uint32_t i = 0; i < threads.size(); i++) { |
|
124 ThreadInfo* info = threads[i]; |
|
125 |
|
126 // This will be null if we're not interested in profiling this thread. |
|
127 if (!info->Profile()) |
|
128 continue; |
|
129 |
|
130 PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); |
|
131 if (sleeping == PseudoStack::SLEEPING_AGAIN) { |
|
132 info->Profile()->DuplicateLastSample(); |
|
133 //XXX: This causes flushes regardless of jank-only mode |
|
134 info->Profile()->flush(); |
|
135 continue; |
|
136 } |
|
137 |
|
138 ThreadProfile* thread_profile = info->Profile(); |
|
139 |
|
140 SampleContext(sampler_, thread_profile); |
|
141 } |
|
142 } |
|
143 OS::Sleep(interval_); |
|
144 } |
|
145 |
|
146 // disable any timer resolution changes we've made |
|
147 if (interval_ < 10) |
|
148 ::timeEndPeriod(interval_); |
|
149 } |
|
150 |
|
151 void SampleContext(Sampler* sampler, ThreadProfile* thread_profile) { |
|
152 uintptr_t thread = Sampler::GetThreadHandle( |
|
153 thread_profile->GetPlatformData()); |
|
154 HANDLE profiled_thread = reinterpret_cast<HANDLE>(thread); |
|
155 if (profiled_thread == NULL) |
|
156 return; |
|
157 |
|
158 // Context used for sampling the register state of the profiled thread. |
|
159 CONTEXT context; |
|
160 memset(&context, 0, sizeof(context)); |
|
161 |
|
162 TickSample sample_obj; |
|
163 TickSample* sample = &sample_obj; |
|
164 |
|
165 // Grab the timestamp before pausing the thread, to avoid deadlocks. |
|
166 sample->timestamp = mozilla::TimeStamp::Now(); |
|
167 sample->threadProfile = thread_profile; |
|
168 |
|
169 static const DWORD kSuspendFailed = static_cast<DWORD>(-1); |
|
170 if (SuspendThread(profiled_thread) == kSuspendFailed) |
|
171 return; |
|
172 |
|
173 context.ContextFlags = CONTEXT_CONTROL; |
|
174 if (GetThreadContext(profiled_thread, &context) != 0) { |
|
175 #if V8_HOST_ARCH_X64 |
|
176 sample->pc = reinterpret_cast<Address>(context.Rip); |
|
177 sample->sp = reinterpret_cast<Address>(context.Rsp); |
|
178 sample->fp = reinterpret_cast<Address>(context.Rbp); |
|
179 #else |
|
180 sample->pc = reinterpret_cast<Address>(context.Eip); |
|
181 sample->sp = reinterpret_cast<Address>(context.Esp); |
|
182 sample->fp = reinterpret_cast<Address>(context.Ebp); |
|
183 #endif |
|
184 sample->context = &context; |
|
185 sampler->Tick(sample); |
|
186 } |
|
187 ResumeThread(profiled_thread); |
|
188 } |
|
189 |
|
190 Sampler* sampler_; |
|
191 int interval_; // units: ms |
|
192 |
|
193 // Protects the process wide state below. |
|
194 static SamplerThread* instance_; |
|
195 |
|
196 DISALLOW_COPY_AND_ASSIGN(SamplerThread); |
|
197 }; |
|
198 |
|
199 SamplerThread* SamplerThread::instance_ = NULL; |
|
200 |
|
201 |
|
202 Sampler::Sampler(double interval, bool profiling, int entrySize) |
|
203 : interval_(interval), |
|
204 profiling_(profiling), |
|
205 paused_(false), |
|
206 active_(false), |
|
207 entrySize_(entrySize) { |
|
208 } |
|
209 |
|
210 Sampler::~Sampler() { |
|
211 ASSERT(!IsActive()); |
|
212 } |
|
213 |
|
214 void Sampler::Start() { |
|
215 ASSERT(!IsActive()); |
|
216 SetActive(true); |
|
217 SamplerThread::StartSampler(this); |
|
218 } |
|
219 |
|
220 void Sampler::Stop() { |
|
221 ASSERT(IsActive()); |
|
222 SetActive(false); |
|
223 SamplerThread::StopSampler(); |
|
224 } |
|
225 |
|
226 |
|
227 static const HANDLE kNoThread = INVALID_HANDLE_VALUE; |
|
228 |
|
229 static unsigned int __stdcall ThreadEntry(void* arg) { |
|
230 Thread* thread = reinterpret_cast<Thread*>(arg); |
|
231 thread->Run(); |
|
232 return 0; |
|
233 } |
|
234 |
|
235 // Initialize a Win32 thread object. The thread has an invalid thread |
|
236 // handle until it is started. |
|
237 Thread::Thread(const char* name) |
|
238 : stack_size_(0) { |
|
239 thread_ = kNoThread; |
|
240 set_name(name); |
|
241 } |
|
242 |
|
243 void Thread::set_name(const char* name) { |
|
244 strncpy(name_, name, sizeof(name_)); |
|
245 name_[sizeof(name_) - 1] = '\0'; |
|
246 } |
|
247 |
|
248 // Close our own handle for the thread. |
|
249 Thread::~Thread() { |
|
250 if (thread_ != kNoThread) CloseHandle(thread_); |
|
251 } |
|
252 |
|
253 // Create a new thread. It is important to use _beginthreadex() instead of |
|
254 // the Win32 function CreateThread(), because the CreateThread() does not |
|
255 // initialize thread specific structures in the C runtime library. |
|
256 void Thread::Start() { |
|
257 thread_ = reinterpret_cast<HANDLE>( |
|
258 _beginthreadex(NULL, |
|
259 static_cast<unsigned>(stack_size_), |
|
260 ThreadEntry, |
|
261 this, |
|
262 0, |
|
263 (unsigned int*) &thread_id_)); |
|
264 } |
|
265 |
|
266 // Wait for thread to terminate. |
|
267 void Thread::Join() { |
|
268 if (thread_id_ != GetCurrentId()) { |
|
269 WaitForSingleObject(thread_, INFINITE); |
|
270 } |
|
271 } |
|
272 |
|
273 /* static */ Thread::tid_t |
|
274 Thread::GetCurrentId() |
|
275 { |
|
276 return GetCurrentThreadId(); |
|
277 } |
|
278 |
|
279 void OS::Startup() { |
|
280 } |
|
281 |
|
282 void OS::Sleep(int milliseconds) { |
|
283 ::Sleep(milliseconds); |
|
284 } |
|
285 |
|
286 bool Sampler::RegisterCurrentThread(const char* aName, |
|
287 PseudoStack* aPseudoStack, |
|
288 bool aIsMainThread, void* stackTop) |
|
289 { |
|
290 if (!Sampler::sRegisteredThreadsMutex) |
|
291 return false; |
|
292 |
|
293 |
|
294 mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); |
|
295 |
|
296 int id = GetCurrentThreadId(); |
|
297 |
|
298 for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { |
|
299 ThreadInfo* info = sRegisteredThreads->at(i); |
|
300 if (info->ThreadId() == id) { |
|
301 // Thread already registered. This means the first unregister will be |
|
302 // too early. |
|
303 ASSERT(false); |
|
304 return false; |
|
305 } |
|
306 } |
|
307 |
|
308 set_tls_stack_top(stackTop); |
|
309 |
|
310 ThreadInfo* info = new ThreadInfo(aName, id, |
|
311 aIsMainThread, aPseudoStack, stackTop); |
|
312 |
|
313 if (sActiveSampler) { |
|
314 sActiveSampler->RegisterThread(info); |
|
315 } |
|
316 |
|
317 sRegisteredThreads->push_back(info); |
|
318 |
|
319 uwt__register_thread_for_profiling(stackTop); |
|
320 return true; |
|
321 } |
|
322 |
|
323 void Sampler::UnregisterCurrentThread() |
|
324 { |
|
325 if (!Sampler::sRegisteredThreadsMutex) |
|
326 return; |
|
327 |
|
328 tlsStackTop.set(nullptr); |
|
329 |
|
330 mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); |
|
331 |
|
332 int id = GetCurrentThreadId(); |
|
333 |
|
334 for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { |
|
335 ThreadInfo* info = sRegisteredThreads->at(i); |
|
336 if (info->ThreadId() == id) { |
|
337 delete info; |
|
338 sRegisteredThreads->erase(sRegisteredThreads->begin() + i); |
|
339 break; |
|
340 } |
|
341 } |
|
342 } |
|
343 |
|
344 void TickSample::PopulateContext(void* aContext) |
|
345 { |
|
346 MOZ_ASSERT(aContext); |
|
347 CONTEXT* pContext = reinterpret_cast<CONTEXT*>(aContext); |
|
348 context = pContext; |
|
349 RtlCaptureContext(pContext); |
|
350 |
|
351 #if defined(SPS_PLAT_amd64_windows) |
|
352 |
|
353 pc = reinterpret_cast<Address>(pContext->Rip); |
|
354 sp = reinterpret_cast<Address>(pContext->Rsp); |
|
355 fp = reinterpret_cast<Address>(pContext->Rbp); |
|
356 |
|
357 #elif defined(SPS_PLAT_x86_windows) |
|
358 |
|
359 pc = reinterpret_cast<Address>(pContext->Eip); |
|
360 sp = reinterpret_cast<Address>(pContext->Esp); |
|
361 fp = reinterpret_cast<Address>(pContext->Ebp); |
|
362 |
|
363 #endif |
|
364 } |
|
365 |