summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/platform-win32.cpp
blob: cfe246ea4028a78bb1d0ade02951cc45fdaf7b48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the
//    distribution.
//  * Neither the name of Google, Inc. nor the names of its contributors
//    may be used to endorse or promote products derived from this
//    software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

#include <windows.h>
#include <mmsystem.h>
#include <process.h>

#include "nsWindowsDllInterceptor.h"
#include "mozilla/StackWalk_windows.h"
#include "mozilla/WindowsVersion.h"

#include <type_traits>

static void PopulateRegsFromContext(Registers& aRegs, CONTEXT* aContext) {
#if defined(GP_ARCH_amd64)
  aRegs.mPC = reinterpret_cast<Address>(aContext->Rip);
  aRegs.mSP = reinterpret_cast<Address>(aContext->Rsp);
  aRegs.mFP = reinterpret_cast<Address>(aContext->Rbp);
  aRegs.mLR = 0;
#elif defined(GP_ARCH_x86)
  aRegs.mPC = reinterpret_cast<Address>(aContext->Eip);
  aRegs.mSP = reinterpret_cast<Address>(aContext->Esp);
  aRegs.mFP = reinterpret_cast<Address>(aContext->Ebp);
  aRegs.mLR = 0;
#elif defined(GP_ARCH_arm64)
  aRegs.mPC = reinterpret_cast<Address>(aContext->Pc);
  aRegs.mSP = reinterpret_cast<Address>(aContext->Sp);
  aRegs.mFP = reinterpret_cast<Address>(aContext->Fp);
  aRegs.mLR = reinterpret_cast<Address>(aContext->Lr);
#else
#  error "bad arch"
#endif
}

// Gets a real (i.e. not pseudo) handle for the current thread, with the
// permissions needed for profiling.
// @return a real HANDLE for the current thread.
static HANDLE GetRealCurrentThreadHandleForProfiling() {
  HANDLE realCurrentThreadHandle;
  if (!::DuplicateHandle(
          ::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(),
          &realCurrentThreadHandle,
          THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION,
          FALSE, 0)) {
    return nullptr;
  }

  return realCurrentThreadHandle;
}

static_assert(
    std::is_same_v<mozilla::profiler::PlatformData::WindowsHandle, HANDLE>);

mozilla::profiler::PlatformData::PlatformData(ProfilerThreadId aThreadId)
    : mProfiledThread(GetRealCurrentThreadHandleForProfiling()) {
  MOZ_ASSERT(aThreadId == ProfilerThreadId::FromNumber(::GetCurrentThreadId()));
}

mozilla::profiler::PlatformData::~PlatformData() {
  if (mProfiledThread) {
    CloseHandle(mProfiledThread);
    mProfiledThread = nullptr;
  }
}

static const HANDLE kNoThread = INVALID_HANDLE_VALUE;

////////////////////////////////////////////////////////////////////////
// BEGIN Sampler target specifics

Sampler::Sampler(PSLockRef aLock) {}

void Sampler::Disable(PSLockRef aLock) {}

static void StreamMetaPlatformSampleUnits(PSLockRef aLock,
                                          SpliceableJSONWriter& aWriter) {
  static const Span<const char> units =
      (GetCycleTimeFrequencyMHz() != 0) ? MakeStringSpan("ns")
                                        : MakeStringSpan("variable CPU cycles");
  aWriter.StringProperty("threadCPUDelta", units);
}

/* static */
uint64_t RunningTimes::ConvertRawToJson(uint64_t aRawValue) {
  static const uint64_t cycleTimeFrequencyMHz = GetCycleTimeFrequencyMHz();
  if (cycleTimeFrequencyMHz == 0u) {
    return aRawValue;
  }

  constexpr uint64_t GHZ_PER_MHZ = 1'000u;
  // To get ns, we need to divide cycles by a frequency in GHz, i.e.:
  // cycles / (f_MHz / GHZ_PER_MHZ). To avoid losing the integer precision of
  // f_MHz, this is computed as (cycles * GHZ_PER_MHZ) / f_MHz.
  // Adding GHZ_PER_MHZ/2 to (cycles * GHZ_PER_MHZ) will round to nearest when
  // the result of the division is truncated.
  return (aRawValue * GHZ_PER_MHZ + (GHZ_PER_MHZ / 2u)) / cycleTimeFrequencyMHz;
}

static inline uint64_t ToNanoSeconds(const FILETIME& aFileTime) {
  // FILETIME values are 100-nanoseconds units, converting
  ULARGE_INTEGER usec = {{aFileTime.dwLowDateTime, aFileTime.dwHighDateTime}};
  return usec.QuadPart * 100;
}

namespace mozilla::profiler {
bool GetCpuTimeSinceThreadStartInNs(
    uint64_t* aResult, const mozilla::profiler::PlatformData& aPlatformData) {
  const HANDLE profiledThread = aPlatformData.ProfiledThread();
  int frequencyInMHz = GetCycleTimeFrequencyMHz();
  if (frequencyInMHz) {
    uint64_t cpuCycleCount;
    if (!QueryThreadCycleTime(profiledThread, &cpuCycleCount)) {
      return false;
    }

    constexpr uint64_t USEC_PER_NSEC = 1000L;
    *aResult = cpuCycleCount * USEC_PER_NSEC / frequencyInMHz;
    return true;
  }

  FILETIME createTime, exitTime, kernelTime, userTime;
  if (!GetThreadTimes(profiledThread, &createTime, &exitTime, &kernelTime,
                      &userTime)) {
    return false;
  }

  *aResult = ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime);
  return true;
}
}  // namespace mozilla::profiler

static RunningTimes GetProcessRunningTimesDiff(
    PSLockRef aLock, RunningTimes& aPreviousRunningTimesToBeUpdated) {
  AUTO_PROFILER_STATS(GetProcessRunningTimes);

  static const HANDLE processHandle = GetCurrentProcess();

  RunningTimes newRunningTimes;
  {
    AUTO_PROFILER_STATS(GetProcessRunningTimes_QueryProcessCycleTime);
    if (ULONG64 cycles; QueryProcessCycleTime(processHandle, &cycles) != 0) {
      newRunningTimes.SetThreadCPUDelta(cycles);
    }
    newRunningTimes.SetPostMeasurementTimeStamp(TimeStamp::Now());
  };

  const RunningTimes diff = newRunningTimes - aPreviousRunningTimesToBeUpdated;
  aPreviousRunningTimesToBeUpdated = newRunningTimes;
  return diff;
}

static RunningTimes GetThreadRunningTimesDiff(
    PSLockRef aLock,
    ThreadRegistration::UnlockedRWForLockedProfiler& aThreadData) {
  AUTO_PROFILER_STATS(GetThreadRunningTimes);

  const mozilla::profiler::PlatformData& platformData =
      aThreadData.PlatformDataCRef();
  const HANDLE profiledThread = platformData.ProfiledThread();

  const RunningTimes newRunningTimes = GetRunningTimesWithTightTimestamp(
      [profiledThread](RunningTimes& aRunningTimes) {
        AUTO_PROFILER_STATS(GetThreadRunningTimes_QueryThreadCycleTime);
        if (ULONG64 cycles;
            QueryThreadCycleTime(profiledThread, &cycles) != 0) {
          aRunningTimes.ResetThreadCPUDelta(cycles);
        } else {
          aRunningTimes.ClearThreadCPUDelta();
        }
      });

  ProfiledThreadData* profiledThreadData =
      aThreadData.GetProfiledThreadData(aLock);
  MOZ_ASSERT(profiledThreadData);
  RunningTimes& previousRunningTimes =
      profiledThreadData->PreviousThreadRunningTimesRef();
  const RunningTimes diff = newRunningTimes - previousRunningTimes;
  previousRunningTimes = newRunningTimes;
  return diff;
}

static void DiscardSuspendedThreadRunningTimes(
    PSLockRef aLock,
    ThreadRegistration::UnlockedRWForLockedProfiler& aThreadData) {
  AUTO_PROFILER_STATS(DiscardSuspendedThreadRunningTimes);

  // On Windows, suspending a thread makes that thread work a little bit. So we
  // want to discard any added running time since the call to
  // GetThreadRunningTimesDiff, which is done by overwriting the thread's
  // PreviousThreadRunningTimesRef() with the current running time now.

  const mozilla::profiler::PlatformData& platformData =
      aThreadData.PlatformDataCRef();
  const HANDLE profiledThread = platformData.ProfiledThread();

  ProfiledThreadData* profiledThreadData =
      aThreadData.GetProfiledThreadData(aLock);
  MOZ_ASSERT(profiledThreadData);
  RunningTimes& previousRunningTimes =
      profiledThreadData->PreviousThreadRunningTimesRef();

  if (ULONG64 cycles; QueryThreadCycleTime(profiledThread, &cycles) != 0) {
    previousRunningTimes.ResetThreadCPUDelta(cycles);
  } else {
    previousRunningTimes.ClearThreadCPUDelta();
  }
}

template <typename Func>
void Sampler::SuspendAndSampleAndResumeThread(
    PSLockRef aLock,
    const ThreadRegistration::UnlockedReaderAndAtomicRWOnThread& aThreadData,
    const TimeStamp& aNow, const Func& aProcessRegs) {
  HANDLE profiled_thread = aThreadData.PlatformDataCRef().ProfiledThread();
  if (profiled_thread == nullptr) {
    return;
  }

  // Context used for sampling the register state of the profiled thread.
  CONTEXT context;
  memset(&context, 0, sizeof(context));

  //----------------------------------------------------------------//
  // Suspend the samplee thread and get its context.

  static const DWORD kSuspendFailed = static_cast<DWORD>(-1);
  if (SuspendThread(profiled_thread) == kSuspendFailed) {
    return;
  }

  // SuspendThread is asynchronous, so the thread may still be running.
  // Call GetThreadContext first to ensure the thread is really suspended.
  // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.

  // Using only CONTEXT_CONTROL is faster but on 64-bit it causes crashes in
  // RtlVirtualUnwind (see bug 1120126) so we set all the flags.
#if defined(GP_ARCH_amd64)
  context.ContextFlags = CONTEXT_FULL;
#else
  context.ContextFlags = CONTEXT_CONTROL;
#endif
  if (!GetThreadContext(profiled_thread, &context)) {
    ResumeThread(profiled_thread);
    return;
  }

  //----------------------------------------------------------------//
  // Sample the target thread.

  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
  //
  // The profiler's "critical section" begins here.  We must be very careful
  // what we do here, or risk deadlock.  See the corresponding comment in
  // platform-linux-android.cpp for details.

  Registers regs;
  PopulateRegsFromContext(regs, &context);
  aProcessRegs(regs, aNow);

  //----------------------------------------------------------------//
  // Resume the target thread.

  ResumeThread(profiled_thread);

  // The profiler's critical section ends here.
  //
  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
}

// END Sampler target specifics
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
// BEGIN SamplerThread target specifics

static unsigned int __stdcall ThreadEntry(void* aArg) {
  auto thread = static_cast<SamplerThread*>(aArg);
  thread->Run();
  return 0;
}

static unsigned int __stdcall UnregisteredThreadSpyEntry(void* aArg) {
  auto thread = static_cast<SamplerThread*>(aArg);
  thread->RunUnregisteredThreadSpy();
  return 0;
}

SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
                             double aIntervalMilliseconds, uint32_t aFeatures)
    : mSampler(aLock),
      mActivityGeneration(aActivityGeneration),
      mIntervalMicroseconds(
          std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5)))),
      mNoTimerResolutionChange(
          ProfilerFeature::HasNoTimerResolutionChange(aFeatures)) {
  if ((!mNoTimerResolutionChange) && (mIntervalMicroseconds < 10 * 1000)) {
    // By default the timer resolution (which tends to be 1/64Hz, around 16ms)
    // is not changed. However, if the requested interval is sufficiently low,
    // the resolution will be adjusted to match. Note that this affects all
    // timers in Firefox, and could therefore hide issues while profiling. This
    // change may be prevented with the "notimerresolutionchange" feature.
    ::timeBeginPeriod(mIntervalMicroseconds / 1000);
  }

  if (ProfilerFeature::HasUnregisteredThreads(aFeatures)) {
    // Sampler&spy threads are not running yet, so it's safe to modify
    // mSpyingState without locking the monitor.
    mSpyingState = SpyingState::Spy_Initializing;
    mUnregisteredThreadSpyThread = reinterpret_cast<HANDLE>(
        _beginthreadex(nullptr,
                       /* stack_size */ 0, UnregisteredThreadSpyEntry, this,
                       /* initflag */ 0, nullptr));
    if (mUnregisteredThreadSpyThread == 0) {
      MOZ_CRASH("_beginthreadex failed");
    }
  }

  // Create a new thread. It is important to use _beginthreadex() instead of
  // the Win32 function CreateThread(), because the CreateThread() does not
  // initialize thread-specific structures in the C runtime library.
  mThread = reinterpret_cast<HANDLE>(_beginthreadex(nullptr,
                                                    /* stack_size */ 0,
                                                    ThreadEntry, this,
                                                    /* initflag */ 0, nullptr));
  if (mThread == 0) {
    MOZ_CRASH("_beginthreadex failed");
  }
}

SamplerThread::~SamplerThread() {
  if (mUnregisteredThreadSpyThread) {
    {
      // Make sure the spying thread is not actively working, because the win32
      // function it's using could deadlock with WaitForSingleObject below.
      MonitorAutoLock spyingStateLock{mSpyingStateMonitor};
      while (mSpyingState != SpyingState::Spy_Waiting &&
             mSpyingState != SpyingState::SamplerToSpy_Start) {
        spyingStateLock.Wait();
      }

      mSpyingState = SpyingState::MainToSpy_Shutdown;
      spyingStateLock.NotifyAll();

      do {
        spyingStateLock.Wait();
      } while (mSpyingState != SpyingState::SpyToMain_ShuttingDown);
    }

    WaitForSingleObject(mUnregisteredThreadSpyThread, INFINITE);

    // Close our own handle for the thread.
    if (mUnregisteredThreadSpyThread != kNoThread) {
      CloseHandle(mUnregisteredThreadSpyThread);
    }
  }

  WaitForSingleObject(mThread, INFINITE);

  // Close our own handle for the thread.
  if (mThread != kNoThread) {
    CloseHandle(mThread);
  }

  // Just in the unlikely case some callbacks were added between the end of the
  // thread and now.
  InvokePostSamplingCallbacks(std::move(mPostSamplingCallbackList),
                              SamplingState::JustStopped);
}

void SamplerThread::RunUnregisteredThreadSpy() {
  // TODO: Consider registering this thread.
  // Pros: Remove from list of unregistered threads; Not useful to profiling
  //       Firefox itself.
  // Cons: Doesn't appear in the profile, so users may miss the expensive CPU
  //       cost of this work on Windows.
  PR_SetCurrentThreadName("UnregisteredThreadSpy");

  while (true) {
    {
      MonitorAutoLock spyingStateLock{mSpyingStateMonitor};
      // Either this is the first loop, or we're looping after working.
      MOZ_ASSERT(mSpyingState == SpyingState::Spy_Initializing ||
                 mSpyingState == SpyingState::Spy_Working);

      // Let everyone know we're waiting, and then wait.
      mSpyingState = SpyingState::Spy_Waiting;
      mSpyingStateMonitor.NotifyAll();
      do {
        spyingStateLock.Wait();
      } while (mSpyingState == SpyingState::Spy_Waiting);

      if (mSpyingState == SpyingState::MainToSpy_Shutdown) {
        mSpyingState = SpyingState::SpyToMain_ShuttingDown;
        mSpyingStateMonitor.NotifyAll();
        break;
      }

      MOZ_ASSERT(mSpyingState == SpyingState::SamplerToSpy_Start);
      mSpyingState = SpyingState::Spy_Working;
    }

    // Do the work without lock, so other threads can read the current state.
    SpyOnUnregisteredThreads();
  }
}

void SamplerThread::SleepMicro(uint32_t aMicroseconds) {
  // For now, keep the old behaviour of minimum Sleep(1), even for
  // smaller-than-usual sleeps after an overshoot, unless the user has
  // explicitly opted into a sub-millisecond profiler interval.
  if (mIntervalMicroseconds >= 1000) {
    ::Sleep(std::max(1u, aMicroseconds / 1000));
  } else {
    TimeStamp start = TimeStamp::Now();
    TimeStamp end = start + TimeDuration::FromMicroseconds(aMicroseconds);

    // First, sleep for as many whole milliseconds as possible.
    if (aMicroseconds >= 1000) {
      ::Sleep(aMicroseconds / 1000);
    }

    // Then, spin until enough time has passed.
    while (TimeStamp::Now() < end) {
      YieldProcessor();
    }
  }
}

void SamplerThread::Stop(PSLockRef aLock) {
  if ((!mNoTimerResolutionChange) && (mIntervalMicroseconds < 10 * 1000)) {
    // Disable any timer resolution changes we've made. Do it now while
    // gPSMutex is locked, i.e. before any other SamplerThread can be created
    // and call ::timeBeginPeriod().
    //
    // It's safe to do this now even though this SamplerThread is still alive,
    // because the next time the main loop of Run() iterates it won't get past
    // the mActivityGeneration check, and so it won't make any more ::Sleep()
    // calls.
    ::timeEndPeriod(mIntervalMicroseconds / 1000);
  }

  mSampler.Disable(aLock);
}

// END SamplerThread target specifics
////////////////////////////////////////////////////////////////////////

static void PlatformInit(PSLockRef aLock) {}

#if defined(HAVE_NATIVE_UNWIND)
void Registers::SyncPopulate() {
  CONTEXT context;
  RtlCaptureContext(&context);
  PopulateRegsFromContext(*this, &context);
}
#endif

#if defined(GP_PLAT_amd64_windows)

// Use InitializeWin64ProfilerHooks from the base profiler.

namespace mozilla {
namespace baseprofiler {
MFBT_API void InitializeWin64ProfilerHooks();
}  // namespace baseprofiler
}  // namespace mozilla

using mozilla::baseprofiler::InitializeWin64ProfilerHooks;

#endif  // defined(GP_PLAT_amd64_windows)