tools/profiler/core/platform-win32.cpp
author Nicholas Nethercote <nnethercote@mozilla.com>
Thu, 16 Feb 2017 15:08:07 +1100
changeset 373479 6e4aa11ecb4d6873c113abf48f0694c9da8150d1
parent 373444 7e6850af9372989b7dd66318076deb6df02ddf55
child 373480 8ac2f3a144257a917ac118499f54c6e18862bd21
permissions -rw-r--r--
Bug 1340928 (part 7) - Factor out gIsActive handling in platform-*.cpp. r=mstange. PlatformStart() and PlatformStop() are currently responsible for setting and clearing gIsActive, but it's better if we do it in profiler_{start,stop}(). The patch also does the following. - Adds some missing emacs/vim modelines. - Makes Platform{Start,Stop}() crash if they have failures. I'm not at all confident that ignoring the errors as is currently done will result in sensible behaviour, so brittleness is better.

/* -*- 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 "ProfileEntry.h"

// Memory profile
#include "nsMemoryReporterManager.h"

class PlatformData {
 public:
  // Get a handle to the calling thread. This is the thread that we are
  // going to profile. We need to make a copy of the handle because we are
  // going to use it in the sampler thread. Using GetThreadHandle() will
  // not work in this case. We're using OpenThread because DuplicateHandle
  // for some reason doesn't work in Chrome's sandbox.
  explicit PlatformData(int aThreadId) : profiled_thread_(OpenThread(
                                               THREAD_GET_CONTEXT |
                                               THREAD_SUSPEND_RESUME |
                                               THREAD_QUERY_INFORMATION,
                                               false,
                                               aThreadId)) {}

  ~PlatformData() {
    if (profiled_thread_ != NULL) {
      CloseHandle(profiled_thread_);
      profiled_thread_ = NULL;
    }
  }

  HANDLE profiled_thread() { return profiled_thread_; }

 private:
  HANDLE profiled_thread_;
};

UniquePlatformData
AllocPlatformData(int aThreadId)
{
  return UniquePlatformData(new PlatformData(aThreadId));
}

void
PlatformDataDestructor::operator()(PlatformData* aData)
{
  delete aData;
}

uintptr_t
GetThreadHandle(PlatformData* aData)
{
  return (uintptr_t) aData->profiled_thread();
}

static const HANDLE kNoThread = INVALID_HANDLE_VALUE;

// The sampler thread controls sampling and runs whenever the profiler is
// active. It periodically runs through all registered threads, finds those
// that should be sampled, then pauses and samples them.
class SamplerThread
{
 public:
  explicit SamplerThread(double aInterval)
    : mThread(kNoThread)
    , mInterval(floor(aInterval + 0.5))
  {
    if (mInterval <= 0) {
      mInterval = 1;
    }
  }

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

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

  // 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.
  void Start() {
    mThread = reinterpret_cast<HANDLE>(
        _beginthreadex(NULL,
                       /* stack_size */ 0,
                       ThreadEntry,
                       this,
                       /* initflag */ 0,
                       (unsigned int*) &mThreadId));
    if (mThread == 0) {
      MOZ_CRASH("_beginthreadex failed");
    }
  }

  void Join() {
    if (mThreadId != Thread::GetCurrentId()) {
      WaitForSingleObject(mThread, INFINITE);
    }
  }

  static void StartSampler(double aInterval) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());
    MOZ_RELEASE_ASSERT(!mInstance);

    mInstance = new SamplerThread(aInterval);
    mInstance->Start();
  }

  static void StopSampler() {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());

    mInstance->Join();
    delete mInstance;
    mInstance = NULL;
  }

  void Run() {
    // This function runs on the sampler thread.

    // By default we'll not adjust the timer resolution which tends to be around
    // 16ms. However, if the requested interval is sufficiently low we'll try to
    // adjust the resolution to match.
    if (mInterval < 10)
        ::timeBeginPeriod(mInterval);

    while (gIsActive) {
      gBuffer->deleteExpiredStoredMarkers();

      if (!gIsPaused) {
        mozilla::StaticMutexAutoLock lock(gRegisteredThreadsMutex);

        bool isFirstProfiledThread = true;
        for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
          ThreadInfo* info = (*gRegisteredThreads)[i];

          // This will be null if we're not interested in profiling this thread.
          if (!info->hasProfile() || info->IsPendingDelete()) {
            continue;
          }

          if (info->Stack()->CanDuplicateLastSampleDueToSleep()) {
            info->DuplicateLastSample();
            continue;
          }

          info->UpdateThreadResponsiveness();

          SampleContext(info, isFirstProfiledThread);
          isFirstProfiledThread = false;
        }
      }
      ::Sleep(mInterval);
    }

    // disable any timer resolution changes we've made
    if (mInterval < 10)
        ::timeEndPeriod(mInterval);
  }

  void SampleContext(ThreadInfo* aThreadInfo, bool isFirstProfiledThread)
  {
    uintptr_t thread = GetThreadHandle(aThreadInfo->GetPlatformData());
    HANDLE profiled_thread = reinterpret_cast<HANDLE>(thread);
    if (profiled_thread == NULL)
      return;

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

    TickSample sample_obj;
    TickSample* sample = &sample_obj;

    // Grab the timestamp before pausing the thread, to avoid deadlocks.
    sample->timestamp = mozilla::TimeStamp::Now();
    sample->threadInfo = aThreadInfo;

    if (isFirstProfiledThread && gProfileMemory) {
      sample->rssMemory = nsMemoryReporterManager::ResidentFast();
    } else {
      sample->rssMemory = 0;
    }

    // Unique Set Size is not supported on Windows.
    sample->ussMemory = 0;

    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;
    }

#if defined(GP_ARCH_amd64)
    sample->pc = reinterpret_cast<Address>(context.Rip);
    sample->sp = reinterpret_cast<Address>(context.Rsp);
    sample->fp = reinterpret_cast<Address>(context.Rbp);
#else
    sample->pc = reinterpret_cast<Address>(context.Eip);
    sample->sp = reinterpret_cast<Address>(context.Esp);
    sample->fp = reinterpret_cast<Address>(context.Ebp);
#endif

    sample->context = &context;

    Tick(sample);

    ResumeThread(profiled_thread);
  }

private:
  HANDLE mThread;
  Thread::tid_t mThreadId;

  int mInterval; // units: ms

  // Protects the process wide state below.
  static SamplerThread* mInstance;

  SamplerThread(const SamplerThread&) = delete;
  void operator=(const SamplerThread&) = delete;
};

SamplerThread* SamplerThread::mInstance = NULL;

static void
PlatformInit()
{
}

static void
PlatformStart(double aInterval)
{
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  SamplerThread::StartSampler(aInterval);
}

static void
PlatformStop()
{
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  SamplerThread::StopSampler();
}

/* static */ Thread::tid_t
Thread::GetCurrentId()
{
  return GetCurrentThreadId();
}

void TickSample::PopulateContext(void* aContext)
{
  MOZ_ASSERT(aContext);
  CONTEXT* pContext = reinterpret_cast<CONTEXT*>(aContext);
  context = pContext;
  RtlCaptureContext(pContext);

#if defined(GP_ARCH_amd64)
  pc = reinterpret_cast<Address>(pContext->Rip);
  sp = reinterpret_cast<Address>(pContext->Rsp);
  fp = reinterpret_cast<Address>(pContext->Rbp);
#elif defined(GP_ARCH_x86)
  pc = reinterpret_cast<Address>(pContext->Eip);
  sp = reinterpret_cast<Address>(pContext->Esp);
  fp = reinterpret_cast<Address>(pContext->Ebp);
#endif
}