#include "chrome_frame/test/perf/chrome_frame_perftest.h"

#include <atlwin.h>
#include <atlhost.h>

#include <map>
#include <string>
#include <vector>

#include "base/debug/trace_event_win.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/win/event_trace_consumer.h"
#include "base/win/event_trace_controller.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "chrome/app/image_pre_reader_win.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/test/base/chrome_process_util.h"
#include "chrome/test/perf/perf_test.h"
#include "chrome/test/ui/ui_perf_test.h"
#include "chrome_frame/chrome_tab.h"
#include "chrome_frame/test_utils.h"
#include "chrome_frame/utils.h"

const wchar_t kSilverlightControlKey[] =
    L"CLSID\\{DFEAF541-F3E1-4c24-ACAC-99C30715084A}\\InprocServer32";

const wchar_t kFlashControlKey[] =
    L"CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32";

using base::TimeDelta;
using base::TimeTicks;

class ChromeFrameActiveXContainer
    : public CWindowImpl<ChromeFrameActiveXContainer, CWindow, CWinTraits <
          WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
          WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,
      public CComObjectRootEx<CComSingleThreadModel>,
      public IPropertyNotifySink {
 public:
  ~ChromeFrameActiveXContainer() {
    if (m_hWnd)
      DestroyWindow();
  }

  DECLARE_WND_CLASS_EX(L"ChromeFrameActiveX_container", 0, 0)

  BEGIN_COM_MAP(ChromeFrameActiveXContainer)
    COM_INTERFACE_ENTRY(IPropertyNotifySink)
  END_COM_MAP()

  BEGIN_MSG_MAP(ChromeFrameActiveXContainer)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
  END_MSG_MAP()

  HRESULT OnMessageCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__;
    OnMessageCallbackImpl(param);
    return S_OK;
  }

  HRESULT OnLoadErrorCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    OnLoadErrorCallbackImpl(param);
    return S_OK;
  }

  HRESULT OnLoadCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    OnLoadCallbackImpl(param);
    return S_OK;
  }

  ChromeFrameActiveXContainer() :
      prop_notify_cookie_(0),
      onmsg_(this, &ChromeFrameActiveXContainer::OnMessageCallback),
      onloaderror_(this, &ChromeFrameActiveXContainer::OnLoadErrorCallback),
      onload_(this, &ChromeFrameActiveXContainer::OnLoadCallback) {
  }

  LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& ) {
    chromeview_.Attach(m_hWnd);
    return 0;
  }

  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {  
    if (prop_notify_cookie_) {
      AtlUnadvise(tab_, IID_IPropertyNotifySink, prop_notify_cookie_);
      prop_notify_cookie_ = 0;
    }

    tab_.Release();
    return 0;
  }

  virtual void OnFinalMessage(HWND ) {
    ::PostQuitMessage(6);
  }

  static const wchar_t* GetWndCaption() {
    return L"ChromeFrame Container";
  }

  STDMETHOD(OnRequestEdit)(DISPID disp_id) {
    OnRequestEditImpl(disp_id);
    return S_OK;
  }

  STDMETHOD(OnChanged)(DISPID disp_id) {
    if (disp_id != DISPID_READYSTATE)
      return S_OK;

    long ready_state;
    HRESULT hr = tab_->get_readyState(&ready_state);
    DCHECK(hr == S_OK);

    OnReadyStateChanged(ready_state);

    if (ready_state == READYSTATE_COMPLETE) {
      if (!starting_url_.empty()) {
        Navigate(starting_url_.c_str());
      } else {
        PostMessage(WM_CLOSE);
      }
    } else if (ready_state == READYSTATE_UNINITIALIZED) {
      DLOG(ERROR) << __FUNCTION__ << " Chrome launch failed.";
    }

    return S_OK;
  }

  void CreateChromeFrameWindow(const std::string& starting_url) {
    starting_url_ = starting_url;
    RECT rc = { 0, 0, 800, 600 };
    Create(NULL, rc);
    DCHECK(m_hWnd);
    ShowWindow(SW_SHOWDEFAULT);
  }

  void CreateControl(bool setup_event_sinks) {
    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
    EXPECT_HRESULT_SUCCEEDED(hr);
    hr = chromeview_.QueryControl(tab_.Receive());
    EXPECT_HRESULT_SUCCEEDED(hr);

    if (setup_event_sinks)
      SetupEventSinks();
  }

  void Navigate(const char* url) {
    BeforeNavigateImpl(url);

    HRESULT hr = tab_->put_src(base::win::ScopedBstr(UTF8ToWide(url).c_str()));
    DCHECK(hr == S_OK) << "Chrome frame NavigateToURL(" << url
                       << base::StringPrintf(L") failed 0x%08X", hr);
  }

  void SetupEventSinks() {
    HRESULT hr = AtlAdvise(tab_, this, IID_IPropertyNotifySink,
                           &prop_notify_cookie_);
    DCHECK(hr == S_OK) << "AtlAdvice for IPropertyNotifySink failed " << hr;

    base::win::ScopedVariant onmessage(onmsg_.ToDispatch());
    base::win::ScopedVariant onloaderror(onloaderror_.ToDispatch());
    base::win::ScopedVariant onload(onload_.ToDispatch());
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onmessage(onmessage));
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onloaderror(onloaderror));
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onload(onload));
  }

 protected:

  virtual void OnReadyStateChanged(long ready_state) {}
  virtual void OnRequestEditImpl(DISPID disp_id) {}

  virtual void OnMessageCallbackImpl(const VARIANT* param) {}

  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
  }
  virtual void BeforeNavigateImpl(const char* url) {}

  CAxWindow chromeview_;
  base::win::ScopedComPtr<IChromeFrame> tab_;
  DWORD prop_notify_cookie_;
  DispCallback<ChromeFrameActiveXContainer> onmsg_;
  DispCallback<ChromeFrameActiveXContainer> onloaderror_;
  DispCallback<ChromeFrameActiveXContainer> onload_;
  std::string starting_url_;
};

class ChromeFrameActiveXContainerPerf : public ChromeFrameActiveXContainer {
 public:
  ChromeFrameActiveXContainerPerf() {}

  void CreateControl(bool setup_event_sinks) {
    perf_initialize_.reset(new PerfTimeLogger("Fully initialized"));
    PerfTimeLogger perf_create("Create Control");

    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
    EXPECT_HRESULT_SUCCEEDED(hr);
    hr = chromeview_.QueryControl(tab_.Receive());
    EXPECT_HRESULT_SUCCEEDED(hr);

    perf_create.Done();
    if (setup_event_sinks)
      SetupEventSinks();
  }

 protected:
  virtual void OnReadyStateChanged(long ready_state) {

    if (ready_state == READYSTATE_COMPLETE) {
      perf_initialize_->Done();
    } else if (ready_state == READYSTATE_INTERACTIVE) {

    } else {
      DCHECK(ready_state != READYSTATE_UNINITIALIZED) << "failed to initialize";
    }
  }

  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
    perf_navigate_->Done();
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
    perf_navigate_->Done();
  }

  virtual void BeforeNavigateImpl(const char* url ) {
    std::string test_name = "Navigate ";
    test_name += url;
    perf_navigate_.reset(new PerfTimeLogger(test_name.c_str()));
  }

  scoped_ptr<PerfTimeLogger> perf_initialize_;
  scoped_ptr<PerfTimeLogger> perf_navigate_;
};

class ChromeFramePerfTestBase : public UIPerfTest {
 public:
  ChromeFramePerfTestBase() {}
 protected:
  scoped_ptr<ScopedChromeFrameRegistrar> chrome_frame_registrar_;
};

class ChromeFrameStartupTest : public ChromeFramePerfTestBase {
 public:
  ChromeFrameStartupTest() {}

  virtual void SetUp() {
    ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &dir_app_));

    chrome_dll_ = dir_app_.Append(L"chrome.dll");
    chrome_exe_ = dir_app_.Append(chrome::kBrowserProcessExecutableName);
    chrome_frame_dll_ = dir_app_.Append(kChromeFrameDllName);
    icu_dll_ = dir_app_.Append(L"icudt.dll");
    ffmpegsumo_dll_ = dir_app_.Append(L"ffmpegsumo.dll");
  }

  void RunStartupTest(const char* graph, const char* trace,
                      const char* startup_url, bool test_cold,
                      int total_binaries,
                      const base::FilePath binaries_to_evict[],
                      bool important, bool ignore_cache_error) {
    const int kNumCycles = 20;

    startup_url_ = startup_url;

    TimeDelta timings[kNumCycles];

    for (int i = 0; i < kNumCycles; ++i) {
      if (test_cold) {
        for (int binary_index = 0; binary_index < total_binaries;
             binary_index++) {
          bool result = EvictFileFromSystemCacheWrapper(
              binaries_to_evict[binary_index]);
          if (!ignore_cache_error) {
            ASSERT_TRUE(result);
          } else if (!result) {
            LOG(ERROR) << GetLastError();
            printf("\nFailed to evict file %ls from cache. Not running test\n",
                   binaries_to_evict[binary_index].value().c_str());
            return;
          }
        }
      }

      TimeTicks start_time, end_time;

      RunStartupTestImpl(&start_time, &end_time);

      timings[i] = end_time - start_time;

      CoFreeUnusedLibraries();

      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
    }

    std::string times;
    for (int i = 0; i < kNumCycles; ++i)
      base::StringAppendF(&times, "%.2f,", timings[i].InMillisecondsF());

    perf_test::PrintResultList(graph, "", trace, times, "ms", important);
  }

  base::FilePath dir_app_;
  base::FilePath chrome_dll_;
  base::FilePath chrome_exe_;
  base::FilePath chrome_frame_dll_;
  base::FilePath icu_dll_;
  base::FilePath ffmpegsumo_dll_;

 protected:

  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {}

  static void ReleaseHostComReferences(CAxWindow& host) {
    CComPtr<IAxWinHostWindow> spWinHost;
    host.QueryHost(&spWinHost);
    ASSERT_TRUE(spWinHost != NULL);

    CAxHostWindow* host_window = static_cast<CAxHostWindow*>(spWinHost.p);
    host_window->ReleaseAll();
    host.DestroyWindow();
  }

  std::string startup_url_;
};

class ChromeFrameStartupTestActiveX : public ChromeFrameStartupTest {
 public:
  virtual void SetUp() {

    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));

    ChromeFrameStartupTest::SetUp();
  }

 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    *start_time = TimeTicks::Now();
    SimpleModule module;
    AtlAxWinInit();
    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
    wnd.CreateChromeFrameWindow(startup_url_);
    wnd.CreateControl(true);
    module.RunMessageLoop();
    *end_time = TimeTicks::Now();
  }
};

class ChromeFrameBinariesLoadTest : public ChromeFrameStartupTestActiveX {
  static const size_t kStepSize = 4 * 1024;
 public:
  enum PreReadType {
    kPreReadNone,
    kPreReadPartial,
    kPreReadFull
  };

  ChromeFrameBinariesLoadTest()
      : pre_read_type_(kPreReadNone),
        step_size_(kStepSize),
        bytes_to_read_(0),
        percentage_to_preread_(25) {}

 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    *start_time = TimeTicks::Now();

    if (pre_read_type_ == kPreReadFull) {
      EXPECT_TRUE(ImagePreReader::PreReadImage(chrome_exe_.value().c_str(),
                                               bytes_to_read_,
                                               step_size_));
      EXPECT_TRUE(ImagePreReader::PreReadImage(chrome_dll_.value().c_str(),
                                               bytes_to_read_,
                                               step_size_));
    } else if (pre_read_type_ == kPreReadPartial) {
      EXPECT_TRUE(
          ImagePreReader::PartialPreReadImage(chrome_exe_.value().c_str(),
                                              percentage_to_preread_,
                                              step_size_));
      EXPECT_TRUE(
          ImagePreReader::PartialPreReadImage(chrome_dll_.value().c_str(),
                                              percentage_to_preread_,
                                              step_size_));
    }

    HMODULE chrome_exe = LoadLibrary(chrome_exe_.value().c_str());
    EXPECT_TRUE(chrome_exe != NULL);

    HMODULE chrome_dll = LoadLibrary(chrome_dll_.value().c_str());
    EXPECT_TRUE(chrome_dll != NULL);

    *end_time = TimeTicks::Now();

    FreeLibrary(chrome_exe);
    FreeLibrary(chrome_dll);
  }

  PreReadType pre_read_type_;
  size_t bytes_to_read_;
  size_t step_size_;
  uint8 percentage_to_preread_;
};

class ChromeFrameStartupTestActiveXReference
    : public ChromeFrameStartupTestActiveX {
 public:

  virtual void SetUp() {

    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();

    ChromeFrameStartupTest::SetUp();

    chrome_frame_dll_ = base::FilePath(
        chrome_frame_registrar_->GetReferenceChromeFrameDllPath());
    DVLOG(1) << __FUNCTION__ << ": " << chrome_frame_dll_.value();
  }

  virtual void TearDown() {

    chrome_frame_registrar_.reset(NULL);
  }
};

class ChromeFrameMemoryTest : public ChromeFramePerfTestBase {

  class ProcessMemoryInfo {
   public:

    ProcessMemoryInfo()
        : process_id_(0),
          virtual_size_(0),
          working_set_size_(0),
          chrome_browser_process_(false),
          chrome_frame_memory_test_instance_(NULL) {}

    ProcessMemoryInfo(base::ProcessId process_id, bool chrome_browser_process,
                     ChromeFrameMemoryTest* memory_test_instance)
        : process_id_(process_id),
          virtual_size_(0),
          working_set_size_(0),
          chrome_browser_process_(chrome_browser_process),
          chrome_frame_memory_test_instance_(memory_test_instance) {}

    bool GetMemoryConsumptionDetails() {
      base::ProcessHandle process_handle;
      if (!base::OpenPrivilegedProcessHandle(process_id_, &process_handle)) {
        NOTREACHED();
      }

      scoped_ptr<ChromeTestProcessMetrics> process_metrics;
      process_metrics.reset(
          ChromeTestProcessMetrics::CreateProcessMetrics(process_handle));

      virtual_size_ = process_metrics->GetPagefileUsage();
      working_set_size_ = process_metrics->GetWorkingSetSize();

      return true;
    }

    void Print(const char* test_name) {
      std::string trace_name(test_name);

      ASSERT_TRUE(chrome_frame_memory_test_instance_ != NULL);

      if (chrome_browser_process_) {
         perf_test::PrintResult("vm_final_browser", "", trace_name + "_vm_b",
             virtual_size_ / 1024, "KB", false );
         perf_test::PrintResult("ws_final_browser", "", trace_name + "_ws_b",
             working_set_size_ / 1024, "KB", false );
      } else if (process_id_ == base::GetCurrentProcId()) {
        perf_test::PrintResult("vm_current_process", "", trace_name + "_vm_c",
            virtual_size_ / 1024, "KB", false );
        perf_test::PrintResult("ws_current_process", "", trace_name + "_ws_c",
            working_set_size_ / 1024, "KB", false );
      }

      printf("\n");
    }

    base::ProcessId process_id_;
    size_t virtual_size_;
    size_t working_set_size_;

    bool chrome_browser_process_;

    ChromeFrameMemoryTest* chrome_frame_memory_test_instance_;
  };

  typedef std::map<DWORD, ProcessMemoryInfo> ProcessMemoryConsumptionMap;

 public:
  ChromeFrameMemoryTest() : current_url_index_(0) {
  }

  virtual void SetUp() {

    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
  }

  void RunTest(const char* test_name, char* urls[], int total_urls) {
    ASSERT_TRUE(urls != NULL);
    ASSERT_GT(total_urls, 0);

    start_commit_charge_ = base::GetSystemCommitCharge();

    for (int i = 0; i < total_urls; i++)
      urls_.push_back(urls[i]);

    std::string url;
    GetNextUrl(&url);
    ASSERT_TRUE(!url.empty());

    StartTest(url, test_name);
  }

  void OnNavigationSuccess(const VARIANT* param) {
    ASSERT_TRUE(param != NULL);
    ASSERT_EQ(VT_BSTR, param->vt);

    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    InitiateNextNavigation();
  }

  void OnNavigationFailure(const VARIANT* param) {
    ASSERT_TRUE(param != NULL);
    ASSERT_EQ(VT_BSTR, param->vt);

    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    InitiateNextNavigation();
  }

 protected:
  bool GetNextUrl(std::string* url) {
    if (current_url_index_ >= urls_.size())
      return false;

    *url = urls_[current_url_index_++];
    return true;
  }

  void InitiateNextNavigation() {

    ChromeProcessList child_processes = GetBrowserChildren();
    ChromeProcessList::iterator index;
    for (index = child_processes.begin(); index != child_processes.end();
         ++index) {
      AccountProcessMemoryUsage(*index);
    }

    Sleep(200);

    std::string url;
    bool next_url = GetNextUrl(&url);
    if (!url.empty()) {
      NavigateImpl(url);
    } else {
      TestCompleted();
    }
  }

  void PrintResults(const char* test_name) {
    PrintMemoryUsageInfo(test_name);
    memory_consumption_map_.clear();

    Sleep(200);

    size_t end_commit_charge = base::GetSystemCommitCharge();
    size_t commit_size = (end_commit_charge - start_commit_charge_) * 1024;

    std::string trace_name(test_name);
    trace_name.append("_cc");

    perf_test::PrintResult("commit_charge", "", trace_name,
                           commit_size / 1024, "KB", true );
    printf("\n");
  }

  base::ProcessId chrome_browser_process_id() {
    base::NamedProcessIterator iter(L"chrome.exe", NULL);
    const base::ProcessEntry* entry = iter.NextProcessEntry();
    if (entry) {
      return entry->pid();
    }
    return -1;
  }

  ChromeProcessList GetBrowserChildren() {
    ChromeProcessList list = GetRunningChromeProcesses(
        chrome_browser_process_id());
    ChromeProcessList::iterator browser =
        std::find(list.begin(), list.end(), chrome_browser_process_id());
    if (browser != list.end()) {
      list.erase(browser);
    }
    return list;
  }

  void AccountProcessMemoryUsage(DWORD process_id) {
    ProcessMemoryInfo process_memory_info(
        process_id, process_id == chrome_browser_process_id(), this);

    ASSERT_TRUE(process_memory_info.GetMemoryConsumptionDetails());

    memory_consumption_map_[process_id] = process_memory_info;
  }

  void PrintMemoryUsageInfo(const char* test_name) {
    printf("\n");

    std::string trace_name(test_name);

    ProcessMemoryConsumptionMap::iterator index;
    size_t total_virtual_size = 0;
    size_t total_working_set_size = 0;

    for (index = memory_consumption_map_.begin();
         index != memory_consumption_map_.end();
         ++index) {
      ProcessMemoryInfo& memory_info = (*index).second;
      memory_info.Print(test_name);

      total_virtual_size += memory_info.virtual_size_;
      total_working_set_size += memory_info.working_set_size_;
    }

    printf("\n");

    perf_test::PrintResult("vm_final_total", "", trace_name + "_vm",
                           total_virtual_size / 1024, "KB",
                           false );
    perf_test::PrintResult("ws_final_total", "", trace_name + "_ws",
                           total_working_set_size / 1024, "KB",
                           true );
  }

  virtual void StartTest(const std::string& url,
                         const std::string& test_name) = 0 {
    ASSERT_FALSE(false);
  }

  virtual void NavigateImpl(const std::string& url) = 0 {
    ASSERT_FALSE(false);
  }

  virtual void TestCompleted() = 0 {
    ASSERT_FALSE(false);
  }

  size_t start_commit_charge_;

  size_t current_url_index_;

  std::vector<std::string> urls_;

  ProcessMemoryConsumptionMap memory_consumption_map_;
};

class ChromeFrameMemoryTestReference : public ChromeFrameMemoryTest {
 public:
  virtual void SetUp() {
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
  }

  virtual void TearDown() {

    chrome_frame_registrar_.reset(NULL);
  }
};

class ChromeFrameActiveXContainerMemory : public ChromeFrameActiveXContainer {
 public:
  ChromeFrameActiveXContainerMemory()
      : delegate_(NULL) {}

  ~ChromeFrameActiveXContainerMemory() {}

  void Initialize(ChromeFrameMemoryTest* delegate) {
    ASSERT_TRUE(delegate != NULL);
    delegate_ = delegate;
  }

 protected:
  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    delegate_->OnNavigationSuccess(param);
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    delegate_->OnNavigationFailure(param);
  }

  ChromeFrameMemoryTest* delegate_;
};

template<class MemoryTestBase>
class ChromeFrameActiveXMemoryTest : public MemoryTestBase {
 public:
  ChromeFrameActiveXMemoryTest()
      : chrome_frame_container_(NULL),
        test_completed_(false) {}

  ~ChromeFrameActiveXMemoryTest() {
  }

  void StartTest(const std::string& url, const std::string& test_name) {
    ASSERT_TRUE(chrome_frame_container_ == NULL);

    test_name_ = test_name;

    SimpleModule module;
    AtlAxWinInit();

    CComObject<ChromeFrameActiveXContainerMemory>::CreateInstance(
        &chrome_frame_container_);
    chrome_frame_container_->AddRef();

    chrome_frame_container_->Initialize(this);

    chrome_frame_container_->CreateChromeFrameWindow(url.c_str());
    chrome_frame_container_->CreateControl(true);

    module.RunMessageLoop();

    chrome_frame_container_->Release();

    PrintResults(test_name_.c_str());

    CoFreeUnusedLibraries();
    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
  }

  void NavigateImpl(const std::string& url) {
    ASSERT_TRUE(chrome_frame_container_ != NULL);
    ASSERT_TRUE(!url.empty());
    chrome_frame_container_->Navigate(url.c_str());
  }

  void TestCompleted() {

    if (!test_completed_) {

      AccountProcessMemoryUsage(chrome_browser_process_id());

      AccountProcessMemoryUsage(GetCurrentProcessId());

      test_completed_ = true;
      EXPECT_TRUE(PostMessage(static_cast<HWND>(*chrome_frame_container_),
                              WM_CLOSE, 0, 0));
    }
  }

 protected:
  CComObject<ChromeFrameActiveXContainerMemory>* chrome_frame_container_;
  std::string test_name_;
  bool test_completed_;
};

class ChromeFrameCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
    wnd.CreateChromeFrameWindow(startup_url_);
    *start_time = TimeTicks::Now();
    wnd.CreateControl(false);
    *end_time = TimeTicks::Now();
  }
};

class ChromeFrameCreationTestReference : public ChromeFrameCreationTest {
 public:

  virtual void SetUp() {
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
    ChromeFrameStartupTest::SetUp();
  }

  virtual void TearDown() {
    chrome_frame_registrar_.reset(NULL);
  }
};

class FlashCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CAxWindow host;
    RECT rc = {0, 0, 800, 600};
    host.Create(NULL, rc, NULL,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
    EXPECT_TRUE(host.m_hWnd != NULL);

    *start_time = TimeTicks::Now();
    HRESULT hr = host.CreateControl(L"ShockwaveFlash.ShockwaveFlash");
    EXPECT_HRESULT_SUCCEEDED(hr);
    *end_time = TimeTicks::Now();

    ReleaseHostComReferences(host);
  }
};

class SilverlightCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CAxWindow host;
    RECT rc = {0, 0, 800, 600};
    host.Create(NULL, rc, NULL,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
    EXPECT_TRUE(host.m_hWnd != NULL);

    *start_time = TimeTicks::Now();
    HRESULT hr = host.CreateControl(L"AgControl.AgControl");
    EXPECT_HRESULT_SUCCEEDED(hr);
    *end_time = TimeTicks::Now();

    ReleaseHostComReferences(host);
  }
};

TEST(ImagePreReader, PreReadImage) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  int64 file_size_64 = 0;
  ASSERT_TRUE(file_util::GetFileSize(current_exe, &file_size_64));
  ASSERT_TRUE(file_size_64 < std::numeric_limits<std::size_t>::max());
  size_t file_size = static_cast<size_t>(file_size_64);

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kStepSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, 0, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size / 4, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size / 2, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size * 2, kStepSize));
}

TEST(ImagePreReader, PartialPreReadImage) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kStepSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 0, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 25, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 50, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 100, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 150, kStepSize));
}

TEST(ImagePreReader, PartialPreReadImageOnDisk) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kChunkSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 0, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 25, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 50, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 100, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 150, kChunkSize));
}

TEST(ImagePreReader, PartialPreReadImageInMemory) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));
  const wchar_t* module_path = current_exe.value().c_str();

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 0));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 25));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 50));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 100));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 150));
}

TEST(ChromeFramePerf, DISABLED_HostActiveX) {

  SimpleModule module;
  AtlAxWinInit();
  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
  wnd.CreateChromeFrameWindow("http:
  wnd.CreateControl(true);
  module.RunMessageLoop();
}

TEST(ChromeFramePerf, DISABLED_HostActiveXInvalidURL) {

  SimpleModule module;
  AtlAxWinInit();
  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
  wnd.CreateChromeFrameWindow("http:
  wnd.CreateControl(true);
  module.RunMessageLoop();
}

TEST_F(ChromeFrameStartupTestActiveX, PerfWarm) {
  RunStartupTest("warm", "t", "about:blank", false , 0, NULL,
                 true , false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfWarm) {
  RunStartupTest("binary_load_warm", "t", "", false , 0, NULL,
                 true , false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfCold) {
  SetConfigInt(L"PreRead", 0);
  base::FilePath binaries_to_evict[] = {
    ffmpegsumo_dll_, chrome_exe_, chrome_dll_, chrome_frame_dll_
  };
  RunStartupTest("cold", "t", "about:blank", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
  DeleteConfigValue(L"PreRead");
}

TEST_F(ChromeFrameStartupTestActiveX, PerfColdPreRead) {
  SetConfigInt(L"PreRead", 1);
  base::FilePath binaries_to_evict[] = {
    ffmpegsumo_dll_, chrome_exe_, chrome_dll_, chrome_frame_dll_
  };
  RunStartupTest("cold_preread", "t", "about:blank", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
  DeleteConfigValue(L"PreRead");
}

TEST_F(ChromeFrameBinariesLoadTest, PerfCold) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  RunStartupTest("binary_load_cold", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPreRead) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadFull;
  RunStartupTest("binary_load_cold_preread", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead15) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 15;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead25) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 25;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead40) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 40;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameStartupTestActiveXReference, PerfWarm) {
  RunStartupTest("warm", "t_ref", "about:blank", false , 0, NULL,
                 true , false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationWarm) {
  RunStartupTest("ChromeFrame_init_warm", "t", "", false , 0,
                 NULL, true , false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationCold) {
  base::FilePath binaries_to_evict[] = {chrome_frame_dll_};
  RunStartupTest("ChromeFrame_init_cold", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , false);
}

TEST_F(ChromeFrameStartupTestActiveXReference,
       PerfChromeFrameInitializationWarm) {
  RunStartupTest("ChromeFrame_init_warm", "t_ref", "", false , 0,
                 NULL, true , false);
}

typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTest>
    RegularChromeFrameActiveXMemoryTest;

TEST_F(RegularChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) {
  char *urls[] = {"about:blank"};
  RunTest("memory_about_blank", urls, arraysize(urls));
}

TEST_F(RegularChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {

  char *urls[] = {
    "http:
    "http:
  };

  RunTest("memory", urls, arraysize(urls));
}

typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTestReference>
    ReferenceBuildChromeFrameActiveXMemoryTest;

TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest,
       DISABLED_MemoryTestAboutBlank) {
  char *urls[] = {"about:blank"};
  RunTest("memory_about_blank_reference", urls, arraysize(urls));
}

TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {

  char *urls[] = {
    "http:
    "http:
  };

  RunTest("memory_reference", urls, arraysize(urls));
}

TEST_F(ChromeFrameCreationTest, PerfWarm) {
  RunStartupTest("creation_warm", "t", "", false , 0,
                 NULL, true , false);
}

TEST_F(ChromeFrameCreationTestReference, PerfWarm) {
  RunStartupTest("creation_warm", "t_ref", "about:blank", false , 0,
                 NULL, true , false);
}

TEST_F(FlashCreationTest, DISABLED_PerfWarm) {
  RunStartupTest("creation_warm", "t_flash", "", false , 0, NULL,
                 true , false);
}

TEST_F(SilverlightCreationTest, DISABLED_PerfWarm) {
  RunStartupTest("creation_warm", "t_silverlight", "", false , 0,
                 NULL, false , false);
}

TEST_F(ChromeFrameCreationTest, PerfCold) {
  base::FilePath binaries_to_evict[] = {chrome_frame_dll_};

  RunStartupTest("creation_cold", "t", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 true , false);
}

TEST_F(FlashCreationTest, PerfCold) {
  base::win::RegKey flash_key(HKEY_CLASSES_ROOT, kFlashControlKey, KEY_READ);

  std::wstring plugin_path;
  ASSERT_EQ(ERROR_SUCCESS, flash_key.ReadValue(L"", &plugin_path));
  ASSERT_FALSE(plugin_path.empty());

  base::FilePath flash_path = base::FilePath(plugin_path);
  base::FilePath binaries_to_evict[] = {flash_path};

  RunStartupTest("creation_cold", "t_flash", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false, true);
}

TEST_F(SilverlightCreationTest, DISABLED_PerfCold) {
  base::win::RegKey silverlight_key(HKEY_CLASSES_ROOT, kSilverlightControlKey,
                                    KEY_READ);

  std::wstring plugin_path;
  ASSERT_EQ(ERROR_SUCCESS, silverlight_key.ReadValue(L"", &plugin_path));
  ASSERT_FALSE(plugin_path.empty());

  base::FilePath silverlight_path = base::FilePath(plugin_path);
  base::FilePath binaries_to_evict[] = {silverlight_path};

  RunStartupTest("creation_cold", "t_silverlight", "", true ,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false , true);
}

namespace {

class TracedEvents {
 public:
  virtual void OnTraceEventBegin(EVENT_TRACE* event) {}
  virtual void OnTraceEventEnd(EVENT_TRACE* event) {}
  virtual void OnTraceEventInstant(EVENT_TRACE* event) {}
};

class EtwConsumer : public base::win::EtwTraceConsumerBase<EtwConsumer> {
 public:
  EtwConsumer() {
    set_delegate(NULL);
  }

  ~EtwConsumer() {
    set_delegate(NULL);
  }

  void set_delegate(TracedEvents* delegate) {
    delegate_ = delegate;
  }

  static void ProcessEvent(EVENT_TRACE* event) {
    DCHECK(delegate_);
    if (event->Header.Guid != base::debug::kTraceEventClass32)
      return;
    if (event->Header.Class.Version != 0)
      return;

    switch (event->Header.Class.Type) {
      case base::debug::kTraceEventTypeBegin:
        delegate_->OnTraceEventBegin(event);
        break;
      case base::debug::kTraceEventTypeEnd:
        delegate_->OnTraceEventEnd(event);
        break;
      case base::debug::kTraceEventTypeInstant:
        delegate_->OnTraceEventInstant(event);
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  static TracedEvents* delegate_;
};

TracedEvents* EtwConsumer::delegate_ = NULL;
};  

class EtwPerfSession {
 public:
  EtwPerfSession() {
  }

  ~EtwPerfSession() {
    file_util::Delete(etl_log_file_, false);
  }

  void Start() {

    base::win::EtwTraceProperties ignore;
    base::win::EtwTraceController::Stop(L"cf_perf", &ignore);
    ASSERT_TRUE(file_util::CreateTemporaryFile(&etl_log_file_));
    ASSERT_HRESULT_SUCCEEDED(controller_.StartFileSession(L"cf_perf",
        etl_log_file_.value().c_str(), false));
    ASSERT_HRESULT_SUCCEEDED(controller_.EnableProvider(
        base::debug::kChromeTraceProviderName,
        TRACE_LEVEL_INFORMATION,
        ~(base::debug::CAPTURE_STACK_TRACE)));
  }

  HRESULT Stop() {
    return controller_.Stop(NULL);
  }

  void AnalyzeOutput(TracedEvents* delegate) {
    EtwConsumer consumer;
    consumer.set_delegate(delegate);
    consumer.OpenFileSession(etl_log_file_.value().c_str());
    consumer.Consume();
    consumer.Close();
  }

  base::FilePath etl_log_file_;
  base::win::EtwTraceController controller_;
};

class MonitorTraceBase {
 public:
  static bool IsMatchingEvent(EVENT_TRACE* event,
                              const base::StringPiece& event_to_compare) {
    return event->MofLength > event_to_compare.size() &&
      (memcmp(event_to_compare.data(), event->MofData,
              event_to_compare.size() + 1) == 0);
  }

  bool is_valid() const {
    return !start_.is_null() && !end_.is_null() && start_ <= end_;
  }

  base::TimeDelta duration() const {
    return end_ - start_;
  }

  base::Time start_;
  base::Time end_;
};

class MonitorTracePair : public MonitorTraceBase,
                         public TracedEvents {
 public:
  MonitorTracePair() : event_(NULL) {
  }

  void set_interesting_event(const char* event) {
    event_ = event;
  }

  virtual void OnTraceEventBegin(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_)) {
      EXPECT_TRUE(start_.is_null());
      start_ = base::Time::FromFileTime(
          reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  virtual void OnTraceEventEnd(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_)) {
      EXPECT_FALSE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      end_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  base::StringPiece event_;
};

class MonitorTraceBetweenEventPair : public MonitorTraceBase,
                                     public TracedEvents {
 public:
  MonitorTraceBetweenEventPair() : event_end_(NULL),
                                   event_start_(NULL) {
  }

  void set_start_event(const char* event) {
    event_start_ = event;
  }

  void set_end_event(const char* event) {
    event_end_ = event;
  }

  virtual void OnTraceEventBegin(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_start_)) {
      EXPECT_TRUE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      start_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    } else if (IsMatchingEvent(event, event_end_)) {
      EXPECT_FALSE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      end_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  virtual void OnTraceEventEnd(EVENT_TRACE* event) {}

  base::StringPiece event_start_;
  base::StringPiece event_end_;
};

void PrintResultsImpl(const std::string& measurement,
                      const std::string& modifier,
                      const std::string& trace,
                      const std::string& values,
                      const std::string& prefix,
                      const std::string& suffix,
                      const std::string& units,
                      bool important) {

  printf("%sRESULT %s%s: %s= %s%s%s %s\n",
         important ? "*" : "", measurement.c_str(), modifier.c_str(),
         trace.c_str(), prefix.c_str(), values.c_str(), suffix.c_str(),
         units.c_str());
}

void PrintResultList(const std::string& measurement,
                     const std::string& modifier,
                     const std::string& trace,
                     const std::string& values,
                     const std::string& units,
                     bool important) {
  PrintResultsImpl(measurement, modifier, trace, values,
      "[", "]", units, important);
}

bool RunSingleTestOutOfProc(const std::string& test_name) {
  base::FilePath path;
  if (!PathService::Get(base::DIR_EXE, &path))
    return false;
  path = path.Append(L"chrome_frame_tests.exe");

  CommandLine cmd_line(path);

  cmd_line.AppendSwitch("gtest_also_run_disabled_tests");
  cmd_line.AppendSwitchASCII("gtest_filter", test_name);

  base::ProcessHandle process_handle;
  if (!base::LaunchProcess(cmd_line, base::LaunchOptions(), &process_handle))
    return false;

  base::TimeDelta test_terminate_timeout = base::TimeDelta::FromMinutes(1);
  int exit_code = 0;
  if (!base::WaitForExitCodeWithTimeout(process_handle, &exit_code,
                                        test_terminate_timeout)) {
    LOG(ERROR) << "Test timeout (" << test_terminate_timeout.InMilliseconds()
               << " ms) exceeded for " << test_name;

    exit_code = -1;  

    base::KillProcess(process_handle, -1, true);
  }

  base::CloseProcessHandle(process_handle);

  return exit_code == 0;
}

template <class Monitor>
void PrintPerfTestResults(const Monitor* monitor,
                          int num_cycles,
                          const char* result_name) {
  std::string times;

  for (int i = 0; i < num_cycles; ++i) {
    ASSERT_TRUE(monitor[i].is_valid());
    base::StringAppendF(&times,
                        "%.2f,",
                        monitor[i].duration().InMillisecondsF());
  }

  PrintResultList(result_name, "", "t", times, "ms", false);
}

TEST(TestAsPerfTest, MetaTag_createproxy) {
  const int kNumCycles = 10;

  MonitorTracePair create_proxy_monitor[kNumCycles];
  MonitorTraceBetweenEventPair browser_main_start_monitor[kNumCycles];
  MonitorTraceBetweenEventPair browser_main_loop_monitor[kNumCycles];
  MonitorTraceBetweenEventPair automation_provider_start_monitor[kNumCycles];
  MonitorTraceBetweenEventPair automation_provider_connect_monitor[kNumCycles];
  MonitorTracePair external_tab_navigate_monitor[kNumCycles];
  MonitorTracePair pre_read_chrome_monitor[kNumCycles];
  MonitorTraceBetweenEventPair renderer_main_monitor[kNumCycles];

  for (int i = 0; i < kNumCycles; ++i) {
    EtwPerfSession perf_session;
    ASSERT_NO_FATAL_FAILURE(perf_session.Start());
    ASSERT_TRUE(RunSingleTestOutOfProc(
        "ChromeFrameTestWithWebServer.FullTabModeIE_MetaTag"));

    create_proxy_monitor[i].set_interesting_event("chromeframe.createproxy");

    browser_main_start_monitor[i].set_start_event("chromeframe.createproxy");
    browser_main_start_monitor[i].set_end_event("BrowserMain");

    browser_main_loop_monitor[i].set_start_event("BrowserMain");
    browser_main_loop_monitor[i].set_end_event("BrowserMain:MESSAGE_LOOP");

    automation_provider_start_monitor[i].set_start_event("BrowserMain");
    automation_provider_start_monitor[i].set_end_event(
        "AutomationProvider::AutomationProvider");

    automation_provider_connect_monitor[i].set_start_event(
        "AutomationProvider::AutomationProvider");
    automation_provider_connect_monitor[i].set_end_event(
        "AutomationProvider::InitializeChannel");

    external_tab_navigate_monitor[i].set_interesting_event(
        "ExternalTabContainerWin::Navigate");

    renderer_main_monitor[i].set_start_event(
        "ExternalTabContainerWin::Navigate");
    renderer_main_monitor[i].set_end_event("RendererMain");

    pre_read_chrome_monitor[i].set_interesting_event("PreReadImage");

    ASSERT_HRESULT_SUCCEEDED(perf_session.Stop());

    perf_session.AnalyzeOutput(&create_proxy_monitor[i]);
    perf_session.AnalyzeOutput(&browser_main_start_monitor[i]);
    perf_session.AnalyzeOutput(&browser_main_loop_monitor[i]);
    perf_session.AnalyzeOutput(&automation_provider_start_monitor[i]);
    perf_session.AnalyzeOutput(&automation_provider_connect_monitor[i]);
    perf_session.AnalyzeOutput(&external_tab_navigate_monitor[i]);
    perf_session.AnalyzeOutput(&pre_read_chrome_monitor[i]);
    perf_session.AnalyzeOutput(&renderer_main_monitor[i]);
  }

  PrintPerfTestResults(create_proxy_monitor, kNumCycles, "createproxy");
  PrintPerfTestResults(browser_main_start_monitor, kNumCycles,
                       "browserstart");
  PrintPerfTestResults(browser_main_loop_monitor, kNumCycles,
                       "browserloop");
  PrintPerfTestResults(automation_provider_start_monitor, kNumCycles,
                       "automationproviderstart");
  PrintPerfTestResults(automation_provider_connect_monitor, kNumCycles,
                       "automationproviderconnect");
  PrintPerfTestResults(external_tab_navigate_monitor, kNumCycles,
                       "externaltabnavigate");
  PrintPerfTestResults(renderer_main_monitor, kNumCycles,
                       "beginrenderermain");
#ifdef NDEBUG
  PrintPerfTestResults(pre_read_chrome_monitor, kNumCycles, "PreReadImage");
#endif  
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome_frame/test/perf/chrome_frame_perftest.h"

#include <atlwin.h>
#include <atlhost.h>

#include <map>
#include <string>
#include <vector>

#include "base/debug/trace_event_win.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/win/event_trace_consumer.h"
#include "base/win/event_trace_controller.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "chrome/app/image_pre_reader_win.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/test/base/chrome_process_util.h"
#include "chrome/test/perf/perf_test.h"
#include "chrome/test/ui/ui_perf_test.h"
#include "chrome_frame/chrome_tab.h"
#include "chrome_frame/test_utils.h"
#include "chrome_frame/utils.h"

const wchar_t kSilverlightControlKey[] =
    L"CLSID\\{DFEAF541-F3E1-4c24-ACAC-99C30715084A}\\InprocServer32";

const wchar_t kFlashControlKey[] =
    L"CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32";

using base::TimeDelta;
using base::TimeTicks;

// This class implements an ActiveX container which hosts the ChromeFrame
// ActiveX control. It provides hooks which can be implemented by derived
// classes for implementing performance measurement, etc.
class ChromeFrameActiveXContainer
    : public CWindowImpl<ChromeFrameActiveXContainer, CWindow, CWinTraits <
          WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
          WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,
      public CComObjectRootEx<CComSingleThreadModel>,
      public IPropertyNotifySink {
 public:
  ~ChromeFrameActiveXContainer() {
    if (m_hWnd)
      DestroyWindow();
  }

  DECLARE_WND_CLASS_EX(L"ChromeFrameActiveX_container", 0, 0)

  BEGIN_COM_MAP(ChromeFrameActiveXContainer)
    COM_INTERFACE_ENTRY(IPropertyNotifySink)
  END_COM_MAP()

  BEGIN_MSG_MAP(ChromeFrameActiveXContainer)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
  END_MSG_MAP()

  HRESULT OnMessageCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__;
    OnMessageCallbackImpl(param);
    return S_OK;
  }

  HRESULT OnLoadErrorCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    OnLoadErrorCallbackImpl(param);
    return S_OK;
  }

  HRESULT OnLoadCallback(const VARIANT* param) {
    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    OnLoadCallbackImpl(param);
    return S_OK;
  }

  ChromeFrameActiveXContainer() :
      prop_notify_cookie_(0),
      onmsg_(this, &ChromeFrameActiveXContainer::OnMessageCallback),
      onloaderror_(this, &ChromeFrameActiveXContainer::OnLoadErrorCallback),
      onload_(this, &ChromeFrameActiveXContainer::OnLoadCallback) {
  }

  LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& ) {
    chromeview_.Attach(m_hWnd);
    return 0;
  }

  // This will be called twice.
  // Once from CAxHostWindow::OnDestroy (through DefWindowProc)
  // and once more from the ATL since CAxHostWindow::OnDestroy claims the
  // message is not handled.
  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {  // NOLINT
    if (prop_notify_cookie_) {
      AtlUnadvise(tab_, IID_IPropertyNotifySink, prop_notify_cookie_);
      prop_notify_cookie_ = 0;
    }

    tab_.Release();
    return 0;
  }

  virtual void OnFinalMessage(HWND /*hWnd*/) {
    ::PostQuitMessage(6);
  }

  static const wchar_t* GetWndCaption() {
    return L"ChromeFrame Container";
  }

  // IPropertyNotifySink
  STDMETHOD(OnRequestEdit)(DISPID disp_id) {
    OnRequestEditImpl(disp_id);
    return S_OK;
  }

  STDMETHOD(OnChanged)(DISPID disp_id) {
    if (disp_id != DISPID_READYSTATE)
      return S_OK;

    long ready_state;
    HRESULT hr = tab_->get_readyState(&ready_state);
    DCHECK(hr == S_OK);

    OnReadyStateChanged(ready_state);

    if (ready_state == READYSTATE_COMPLETE) {
      if (!starting_url_.empty()) {
        Navigate(starting_url_.c_str());
      } else {
        PostMessage(WM_CLOSE);
      }
    } else if (ready_state == READYSTATE_UNINITIALIZED) {
      DLOG(ERROR) << __FUNCTION__ << " Chrome launch failed.";
    }

    return S_OK;
  }

  void CreateChromeFrameWindow(const std::string& starting_url) {
    starting_url_ = starting_url;
    RECT rc = { 0, 0, 800, 600 };
    Create(NULL, rc);
    DCHECK(m_hWnd);
    ShowWindow(SW_SHOWDEFAULT);
  }

  void CreateControl(bool setup_event_sinks) {
    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
    EXPECT_HRESULT_SUCCEEDED(hr);
    hr = chromeview_.QueryControl(tab_.Receive());
    EXPECT_HRESULT_SUCCEEDED(hr);

    if (setup_event_sinks)
      SetupEventSinks();
  }

  void Navigate(const char* url) {
    BeforeNavigateImpl(url);

    HRESULT hr = tab_->put_src(base::win::ScopedBstr(UTF8ToWide(url).c_str()));
    DCHECK(hr == S_OK) << "Chrome frame NavigateToURL(" << url
                       << base::StringPrintf(L") failed 0x%08X", hr);
  }

  void SetupEventSinks() {
    HRESULT hr = AtlAdvise(tab_, this, IID_IPropertyNotifySink,
                           &prop_notify_cookie_);
    DCHECK(hr == S_OK) << "AtlAdvice for IPropertyNotifySink failed " << hr;

    base::win::ScopedVariant onmessage(onmsg_.ToDispatch());
    base::win::ScopedVariant onloaderror(onloaderror_.ToDispatch());
    base::win::ScopedVariant onload(onload_.ToDispatch());
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onmessage(onmessage));
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onloaderror(onloaderror));
    EXPECT_HRESULT_SUCCEEDED(tab_->put_onload(onload));
  }

 protected:
  // These functions are implemented by derived classes for special behavior
  // like performance measurement, etc.
  virtual void OnReadyStateChanged(long ready_state) {}
  virtual void OnRequestEditImpl(DISPID disp_id) {}

  virtual void OnMessageCallbackImpl(const VARIANT* param) {}

  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
  }
  virtual void BeforeNavigateImpl(const char* url) {}

  CAxWindow chromeview_;
  base::win::ScopedComPtr<IChromeFrame> tab_;
  DWORD prop_notify_cookie_;
  DispCallback<ChromeFrameActiveXContainer> onmsg_;
  DispCallback<ChromeFrameActiveXContainer> onloaderror_;
  DispCallback<ChromeFrameActiveXContainer> onload_;
  std::string starting_url_;
};

// This class overrides the hooks provided by the ChromeFrameActiveXContainer
// class and measures performance at various stages, like initialzation of
// the Chrome frame widget, navigation, etc.
class ChromeFrameActiveXContainerPerf : public ChromeFrameActiveXContainer {
 public:
  ChromeFrameActiveXContainerPerf() {}

  void CreateControl(bool setup_event_sinks) {
    perf_initialize_.reset(new PerfTimeLogger("Fully initialized"));
    PerfTimeLogger perf_create("Create Control");

    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
    EXPECT_HRESULT_SUCCEEDED(hr);
    hr = chromeview_.QueryControl(tab_.Receive());
    EXPECT_HRESULT_SUCCEEDED(hr);

    perf_create.Done();
    if (setup_event_sinks)
      SetupEventSinks();
  }

 protected:
  virtual void OnReadyStateChanged(long ready_state) {
    // READYSTATE_COMPLETE is fired when the automation server is ready.
    if (ready_state == READYSTATE_COMPLETE) {
      perf_initialize_->Done();
    } else if (ready_state == READYSTATE_INTERACTIVE) {
      // Window ready.  Currently we never receive this notification because it
      // is fired before we finish setting up our hosting environment.
      // This is because of how ATL is written.  Moving forward we might
      // have our own hosting classes and then have more control over when we
      // set up the prop notify sink.
    } else {
      DCHECK(ready_state != READYSTATE_UNINITIALIZED) << "failed to initialize";
    }
  }

  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
    perf_navigate_->Done();
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    PostMessage(WM_CLOSE);
    perf_navigate_->Done();
  }

  virtual void BeforeNavigateImpl(const char* url ) {
    std::string test_name = "Navigate ";
    test_name += url;
    perf_navigate_.reset(new PerfTimeLogger(test_name.c_str()));
  }

  scoped_ptr<PerfTimeLogger> perf_initialize_;
  scoped_ptr<PerfTimeLogger> perf_navigate_;
};

// This class provides common functionality which can be used for most of the
// ChromeFrame/Tab performance tests.
class ChromeFramePerfTestBase : public UIPerfTest {
 public:
  ChromeFramePerfTestBase() {}
 protected:
  scoped_ptr<ScopedChromeFrameRegistrar> chrome_frame_registrar_;
};

class ChromeFrameStartupTest : public ChromeFramePerfTestBase {
 public:
  ChromeFrameStartupTest() {}

  virtual void SetUp() {
    ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &dir_app_));

    chrome_dll_ = dir_app_.Append(L"chrome.dll");
    chrome_exe_ = dir_app_.Append(chrome::kBrowserProcessExecutableName);
    chrome_frame_dll_ = dir_app_.Append(kChromeFrameDllName);
    icu_dll_ = dir_app_.Append(L"icudt.dll");
    ffmpegsumo_dll_ = dir_app_.Append(L"ffmpegsumo.dll");
  }

  // TODO(iyengar)
  // This function is similar to the RunStartupTest function used in chrome
  // startup tests. Refactor into a common implementation.
  void RunStartupTest(const char* graph, const char* trace,
                      const char* startup_url, bool test_cold,
                      int total_binaries,
                      const base::FilePath binaries_to_evict[],
                      bool important, bool ignore_cache_error) {
    const int kNumCycles = 20;

    startup_url_ = startup_url;

    TimeDelta timings[kNumCycles];

    for (int i = 0; i < kNumCycles; ++i) {
      if (test_cold) {
        for (int binary_index = 0; binary_index < total_binaries;
             binary_index++) {
          bool result = EvictFileFromSystemCacheWrapper(
              binaries_to_evict[binary_index]);
          if (!ignore_cache_error) {
            ASSERT_TRUE(result);
          } else if (!result) {
            LOG(ERROR) << GetLastError();
            printf("\nFailed to evict file %ls from cache. Not running test\n",
                   binaries_to_evict[binary_index].value().c_str());
            return;
          }
        }
      }

      TimeTicks start_time, end_time;

      RunStartupTestImpl(&start_time, &end_time);

      timings[i] = end_time - start_time;

      CoFreeUnusedLibraries();

      // TODO(beng): Can't shut down so quickly. Figure out why, and fix. If we
      // do, we crash.
      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
    }

    std::string times;
    for (int i = 0; i < kNumCycles; ++i)
      base::StringAppendF(&times, "%.2f,", timings[i].InMillisecondsF());

    perf_test::PrintResultList(graph, "", trace, times, "ms", important);
  }

  base::FilePath dir_app_;
  base::FilePath chrome_dll_;
  base::FilePath chrome_exe_;
  base::FilePath chrome_frame_dll_;
  base::FilePath icu_dll_;
  base::FilePath ffmpegsumo_dll_;

 protected:
  // Individual startup tests should implement this function.
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {}

  // The host is torn down by this function. It should not be used after
  // this function returns.
  static void ReleaseHostComReferences(CAxWindow& host) {
    CComPtr<IAxWinHostWindow> spWinHost;
    host.QueryHost(&spWinHost);
    ASSERT_TRUE(spWinHost != NULL);

    // Hack to get the host to release all interfaces and thus ensure that
    // the COM server can be unloaded.
    CAxHostWindow* host_window = static_cast<CAxHostWindow*>(spWinHost.p);
    host_window->ReleaseAll();
    host.DestroyWindow();
  }

  std::string startup_url_;
};

class ChromeFrameStartupTestActiveX : public ChromeFrameStartupTest {
 public:
  virtual void SetUp() {
    // Register the Chrome Frame DLL in the build directory.
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));

    ChromeFrameStartupTest::SetUp();
  }

 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    *start_time = TimeTicks::Now();
    SimpleModule module;
    AtlAxWinInit();
    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
    wnd.CreateChromeFrameWindow(startup_url_);
    wnd.CreateControl(true);
    module.RunMessageLoop();
    *end_time = TimeTicks::Now();
  }
};

// This class measures the load time of chrome and chrome frame binaries
class ChromeFrameBinariesLoadTest : public ChromeFrameStartupTestActiveX {
  static const size_t kStepSize = 4 * 1024;
 public:
  enum PreReadType {
    kPreReadNone,
    kPreReadPartial,
    kPreReadFull
  };

  ChromeFrameBinariesLoadTest()
      : pre_read_type_(kPreReadNone),
        step_size_(kStepSize),
        bytes_to_read_(0),
        percentage_to_preread_(25) {}

 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    *start_time = TimeTicks::Now();

    if (pre_read_type_ == kPreReadFull) {
      EXPECT_TRUE(ImagePreReader::PreReadImage(chrome_exe_.value().c_str(),
                                               bytes_to_read_,
                                               step_size_));
      EXPECT_TRUE(ImagePreReader::PreReadImage(chrome_dll_.value().c_str(),
                                               bytes_to_read_,
                                               step_size_));
    } else if (pre_read_type_ == kPreReadPartial) {
      EXPECT_TRUE(
          ImagePreReader::PartialPreReadImage(chrome_exe_.value().c_str(),
                                              percentage_to_preread_,
                                              step_size_));
      EXPECT_TRUE(
          ImagePreReader::PartialPreReadImage(chrome_dll_.value().c_str(),
                                              percentage_to_preread_,
                                              step_size_));
    }

    HMODULE chrome_exe = LoadLibrary(chrome_exe_.value().c_str());
    EXPECT_TRUE(chrome_exe != NULL);

    HMODULE chrome_dll = LoadLibrary(chrome_dll_.value().c_str());
    EXPECT_TRUE(chrome_dll != NULL);

    *end_time = TimeTicks::Now();

    FreeLibrary(chrome_exe);
    FreeLibrary(chrome_dll);
  }

  PreReadType pre_read_type_;
  size_t bytes_to_read_;
  size_t step_size_;
  uint8 percentage_to_preread_;
};

// This class provides functionality to run the startup performance test for
// the ChromeFrame ActiveX against a reference build. At this point we only run
// this test in warm mode.
class ChromeFrameStartupTestActiveXReference
    : public ChromeFrameStartupTestActiveX {
 public:
  // override the browser directory to use the reference build instead.
  virtual void SetUp() {
    // Register the reference build Chrome Frame DLL.
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();

    ChromeFrameStartupTest::SetUp();

    chrome_frame_dll_ = base::FilePath(
        chrome_frame_registrar_->GetReferenceChromeFrameDllPath());
    DVLOG(1) << __FUNCTION__ << ": " << chrome_frame_dll_.value();
  }

  virtual void TearDown() {
    // Reregister the Chrome Frame DLL in the build directory.
    chrome_frame_registrar_.reset(NULL);
  }
};

// This class provides base functionality to measure ChromeFrame memory
// usage.
// TODO(iyengar)
// Some of the functionality in this class like printing the results, etc
// is based on the chrome\test\memory_test.cc. We need to factor out
// the common code.
class ChromeFrameMemoryTest : public ChromeFramePerfTestBase {
  // Contains information about the memory consumption of a process.
  class ProcessMemoryInfo {
   public:
    // Default constructor
    // Added to enable us to add ProcessMemoryInfo instances to a map.
    ProcessMemoryInfo()
        : process_id_(0),
          virtual_size_(0),
          working_set_size_(0),
          chrome_browser_process_(false),
          chrome_frame_memory_test_instance_(NULL) {}

    ProcessMemoryInfo(base::ProcessId process_id, bool chrome_browser_process,
                     ChromeFrameMemoryTest* memory_test_instance)
        : process_id_(process_id),
          virtual_size_(0),
          working_set_size_(0),
          chrome_browser_process_(chrome_browser_process),
          chrome_frame_memory_test_instance_(memory_test_instance) {}

    bool GetMemoryConsumptionDetails() {
      base::ProcessHandle process_handle;
      if (!base::OpenPrivilegedProcessHandle(process_id_, &process_handle)) {
        NOTREACHED();
      }

      // TODO(sgk):  if/when base::ProcessMetrics can return real memory
      // stats on mac, convert to:
      //
      // scoped_ptr<base::ProcessMetrics> process_metrics;
      // process_metrics.reset(
      //     base::ProcessMetrics::CreateProcessMetrics(process_handle));
      scoped_ptr<ChromeTestProcessMetrics> process_metrics;
      process_metrics.reset(
          ChromeTestProcessMetrics::CreateProcessMetrics(process_handle));

      virtual_size_ = process_metrics->GetPagefileUsage();
      working_set_size_ = process_metrics->GetWorkingSetSize();

      return true;
    }

    void Print(const char* test_name) {
      std::string trace_name(test_name);

      ASSERT_TRUE(chrome_frame_memory_test_instance_ != NULL);

      if (chrome_browser_process_) {
         perf_test::PrintResult("vm_final_browser", "", trace_name + "_vm_b",
             virtual_size_ / 1024, "KB", false /* not important */);
         perf_test::PrintResult("ws_final_browser", "", trace_name + "_ws_b",
             working_set_size_ / 1024, "KB", false /* not important */);
      } else if (process_id_ == base::GetCurrentProcId()) {
        perf_test::PrintResult("vm_current_process", "", trace_name + "_vm_c",
            virtual_size_ / 1024, "KB", false /* not important */);
        perf_test::PrintResult("ws_current_process", "", trace_name + "_ws_c",
            working_set_size_ / 1024, "KB", false /* not important */);
      }

      printf("\n");
    }

    base::ProcessId process_id_;
    size_t virtual_size_;
    size_t working_set_size_;
    // Set to true if this is the chrome browser process.
    bool chrome_browser_process_;

    // A reference to the ChromeFrameMemoryTest instance. Used to print memory
    // consumption information.
    ChromeFrameMemoryTest* chrome_frame_memory_test_instance_;
  };

  // This map tracks memory usage for a process. It is keyed on the process
  // id.
  typedef std::map<DWORD, ProcessMemoryInfo> ProcessMemoryConsumptionMap;

 public:
  ChromeFrameMemoryTest() : current_url_index_(0) {
  }

  virtual void SetUp() {
    // Register the Chrome Frame DLL in the build directory.
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
  }

  void RunTest(const char* test_name, char* urls[], int total_urls) {
    ASSERT_TRUE(urls != NULL);
    ASSERT_GT(total_urls, 0);

    // Record the initial CommitCharge.  This is a system-wide measurement,
    // so if other applications are running, they can create variance in this
    // test.
    start_commit_charge_ = base::GetSystemCommitCharge();

    for (int i = 0; i < total_urls; i++)
      urls_.push_back(urls[i]);

    std::string url;
    GetNextUrl(&url);
    ASSERT_TRUE(!url.empty());

    StartTest(url, test_name);
  }

  void OnNavigationSuccess(const VARIANT* param) {
    ASSERT_TRUE(param != NULL);
    ASSERT_EQ(VT_BSTR, param->vt);

    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    InitiateNextNavigation();
  }

  void OnNavigationFailure(const VARIANT* param) {
    ASSERT_TRUE(param != NULL);
    ASSERT_EQ(VT_BSTR, param->vt);

    DVLOG(1) << __FUNCTION__ << " " << param->bstrVal;
    InitiateNextNavigation();
  }

 protected:
  bool GetNextUrl(std::string* url) {
    if (current_url_index_ >= urls_.size())
      return false;

    *url = urls_[current_url_index_++];
    return true;
  }

  void InitiateNextNavigation() {
    // Get the memory consumption information for the child processes
    // of the chrome browser.
    ChromeProcessList child_processes = GetBrowserChildren();
    ChromeProcessList::iterator index;
    for (index = child_processes.begin(); index != child_processes.end();
         ++index) {
      AccountProcessMemoryUsage(*index);
    }

    // TODO(iyengar): Bug 2953
    // Need to verify if this is still true.
    // The automation crashes periodically if we cycle too quickly.
    // To make these tests more reliable, slowing them down a bit.
    Sleep(200);

    std::string url;
    bool next_url = GetNextUrl(&url);
    if (!url.empty()) {
      NavigateImpl(url);
    } else {
      TestCompleted();
    }
  }

  void PrintResults(const char* test_name) {
    PrintMemoryUsageInfo(test_name);
    memory_consumption_map_.clear();

    // Added to give the OS some time to flush the used pages for the
    // chrome processes which would have exited by now.
    Sleep(200);

    size_t end_commit_charge = base::GetSystemCommitCharge();
    size_t commit_size = (end_commit_charge - start_commit_charge_) * 1024;

    std::string trace_name(test_name);
    trace_name.append("_cc");

    perf_test::PrintResult("commit_charge", "", trace_name,
                           commit_size / 1024, "KB", true /* important */);
    printf("\n");
  }

  base::ProcessId chrome_browser_process_id() {
    base::NamedProcessIterator iter(L"chrome.exe", NULL);
    const base::ProcessEntry* entry = iter.NextProcessEntry();
    if (entry) {
      return entry->pid();
    }
    return -1;
  }

  ChromeProcessList GetBrowserChildren() {
    ChromeProcessList list = GetRunningChromeProcesses(
        chrome_browser_process_id());
    ChromeProcessList::iterator browser =
        std::find(list.begin(), list.end(), chrome_browser_process_id());
    if (browser != list.end()) {
      list.erase(browser);
    }
    return list;
  }

  void AccountProcessMemoryUsage(DWORD process_id) {
    ProcessMemoryInfo process_memory_info(
        process_id, process_id == chrome_browser_process_id(), this);

    ASSERT_TRUE(process_memory_info.GetMemoryConsumptionDetails());

    memory_consumption_map_[process_id] = process_memory_info;
  }

  void PrintMemoryUsageInfo(const char* test_name) {
    printf("\n");

    std::string trace_name(test_name);

    ProcessMemoryConsumptionMap::iterator index;
    size_t total_virtual_size = 0;
    size_t total_working_set_size = 0;

    for (index = memory_consumption_map_.begin();
         index != memory_consumption_map_.end();
         ++index) {
      ProcessMemoryInfo& memory_info = (*index).second;
      memory_info.Print(test_name);

      total_virtual_size += memory_info.virtual_size_;
      total_working_set_size += memory_info.working_set_size_;
    }

    printf("\n");

    perf_test::PrintResult("vm_final_total", "", trace_name + "_vm",
                           total_virtual_size / 1024, "KB",
                           false /* not important */);
    perf_test::PrintResult("ws_final_total", "", trace_name + "_ws",
                           total_working_set_size / 1024, "KB",
                           true /* important */);
  }

  // Should never get called.
  virtual void StartTest(const std::string& url,
                         const std::string& test_name) = 0 {
    ASSERT_FALSE(false);
  }

  // Should never get called.
  virtual void NavigateImpl(const std::string& url) = 0 {
    ASSERT_FALSE(false);
  }

  virtual void TestCompleted() = 0 {
    ASSERT_FALSE(false);
  }

  // Holds the commit charge in KBytes at the start of the memory test run.
  size_t start_commit_charge_;

  // The index of the URL being tested.
  size_t current_url_index_;

  // Contains the list of urls against which the tests are run.
  std::vector<std::string> urls_;

  ProcessMemoryConsumptionMap memory_consumption_map_;
};

// This class provides functionality to run the memory test against a reference
// chrome frame build.
class ChromeFrameMemoryTestReference : public ChromeFrameMemoryTest {
 public:
  virtual void SetUp() {
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
  }

  virtual void TearDown() {
    // Reregisters the chrome frame DLL in the build directory.
    chrome_frame_registrar_.reset(NULL);
  }
};

// This class overrides the hooks provided by the ChromeFrameActiveXContainer
// class and calls back into the ChromeFrameMemoryTest object instance,
// which measures ChromeFrame memory usage.
class ChromeFrameActiveXContainerMemory : public ChromeFrameActiveXContainer {
 public:
  ChromeFrameActiveXContainerMemory()
      : delegate_(NULL) {}

  ~ChromeFrameActiveXContainerMemory() {}

  void Initialize(ChromeFrameMemoryTest* delegate) {
    ASSERT_TRUE(delegate != NULL);
    delegate_ = delegate;
  }

 protected:
  virtual void OnLoadCallbackImpl(const VARIANT* param) {
    delegate_->OnNavigationSuccess(param);
  }

  virtual void OnLoadErrorCallbackImpl(const VARIANT* param) {
    delegate_->OnNavigationFailure(param);
  }

  ChromeFrameMemoryTest* delegate_;
};

// This class runs memory tests against the ChromeFrame ActiveX.
template<class MemoryTestBase>
class ChromeFrameActiveXMemoryTest : public MemoryTestBase {
 public:
  ChromeFrameActiveXMemoryTest()
      : chrome_frame_container_(NULL),
        test_completed_(false) {}

  ~ChromeFrameActiveXMemoryTest() {
  }

  void StartTest(const std::string& url, const std::string& test_name) {
    ASSERT_TRUE(chrome_frame_container_ == NULL);

    test_name_ = test_name;

    SimpleModule module;
    AtlAxWinInit();

    CComObject<ChromeFrameActiveXContainerMemory>::CreateInstance(
        &chrome_frame_container_);
    chrome_frame_container_->AddRef();

    chrome_frame_container_->Initialize(this);

    chrome_frame_container_->CreateChromeFrameWindow(url.c_str());
    chrome_frame_container_->CreateControl(true);

    module.RunMessageLoop();

    chrome_frame_container_->Release();

    PrintResults(test_name_.c_str());

    CoFreeUnusedLibraries();
    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
  }

  void NavigateImpl(const std::string& url) {
    ASSERT_TRUE(chrome_frame_container_ != NULL);
    ASSERT_TRUE(!url.empty());
    chrome_frame_container_->Navigate(url.c_str());
  }

  void TestCompleted() {
    // This can get called multiple times if the last url results in a
    // redirect.
    if (!test_completed_) {
      // Measure memory usage for the browser process.
      AccountProcessMemoryUsage(chrome_browser_process_id());
      // Measure memory usage for the current process.
      AccountProcessMemoryUsage(GetCurrentProcessId());

      test_completed_ = true;
      EXPECT_TRUE(PostMessage(static_cast<HWND>(*chrome_frame_container_),
                              WM_CLOSE, 0, 0));
    }
  }

 protected:
  CComObject<ChromeFrameActiveXContainerMemory>* chrome_frame_container_;
  std::string test_name_;
  bool test_completed_;
};

// This class runs tests to measure chrome frame creation only. This will help
// track overall page load performance with chrome frame instances.
class ChromeFrameCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
    wnd.CreateChromeFrameWindow(startup_url_);
    *start_time = TimeTicks::Now();
    wnd.CreateControl(false);
    *end_time = TimeTicks::Now();
  }
};

// This class provides functionality to run the chrome frame
// performance test against a reference build.
class ChromeFrameCreationTestReference : public ChromeFrameCreationTest {
 public:
  // override the browser directory to use the reference build instead.
  virtual void SetUp() {
    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar(
        ScopedChromeFrameRegistrar::SYSTEM_LEVEL));
    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
    ChromeFrameStartupTest::SetUp();
  }

  virtual void TearDown() {
    chrome_frame_registrar_.reset(NULL);
  }
};

// This class measures the creation time for Flash, which would be used
// as a baseline to measure chrome frame creation performance.
class FlashCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CAxWindow host;
    RECT rc = {0, 0, 800, 600};
    host.Create(NULL, rc, NULL,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
    EXPECT_TRUE(host.m_hWnd != NULL);

    *start_time = TimeTicks::Now();
    HRESULT hr = host.CreateControl(L"ShockwaveFlash.ShockwaveFlash");
    EXPECT_HRESULT_SUCCEEDED(hr);
    *end_time = TimeTicks::Now();

    ReleaseHostComReferences(host);
  }
};

// This class measures the creation time for Silverlight, which would be used
// as a baseline to measure chrome frame creation performance.
class SilverlightCreationTest : public ChromeFrameStartupTest {
 protected:
  virtual void RunStartupTestImpl(TimeTicks* start_time,
                                  TimeTicks* end_time) {
    SimpleModule module;
    AtlAxWinInit();
    CAxWindow host;
    RECT rc = {0, 0, 800, 600};
    host.Create(NULL, rc, NULL,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
    EXPECT_TRUE(host.m_hWnd != NULL);

    *start_time = TimeTicks::Now();
    HRESULT hr = host.CreateControl(L"AgControl.AgControl");
    EXPECT_HRESULT_SUCCEEDED(hr);
    *end_time = TimeTicks::Now();

    ReleaseHostComReferences(host);
  }
};

// TODO(rogerm): Flesh out the *PreReadImage* tests to validate an observed
//     change in paging behaviour between raw loading and pre-reading.

// TODO(rogerm): Add checks to the *PreReadImage* tests to validate the
//     handling of invalid pe files and paths as input.

TEST(ImagePreReader, PreReadImage) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  int64 file_size_64 = 0;
  ASSERT_TRUE(file_util::GetFileSize(current_exe, &file_size_64));
  ASSERT_TRUE(file_size_64 < std::numeric_limits<std::size_t>::max());
  size_t file_size = static_cast<size_t>(file_size_64);

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kStepSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, 0, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size / 4, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size / 2, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PreReadImage(module_path, file_size * 2, kStepSize));
}

TEST(ImagePreReader, PartialPreReadImage) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kStepSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 0, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 25, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 50, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 100, kStepSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImage(module_path, 150, kStepSize));
}

TEST(ImagePreReader, PartialPreReadImageOnDisk) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));

  const wchar_t* module_path = current_exe.value().c_str();
  const size_t kChunkSize = 2 * 1024 * 1024;

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 0, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 25, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 50, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 100, kChunkSize));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageOnDisk(module_path, 150, kChunkSize));
}

TEST(ImagePreReader, PartialPreReadImageInMemory) {
  base::FilePath current_exe;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &current_exe));
  const wchar_t* module_path = current_exe.value().c_str();

  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 0));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 25));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 50));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 100));
  ASSERT_TRUE(
      ImagePreReader::PartialPreReadImageInMemory(module_path, 150));
}

TEST(ChromeFramePerf, DISABLED_HostActiveX) {
  // TODO(stoyan): Create a low integrity level thread && perform the test there
  SimpleModule module;
  AtlAxWinInit();
  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
  wnd.CreateChromeFrameWindow("http://www.google.com");
  wnd.CreateControl(true);
  module.RunMessageLoop();
}

TEST(ChromeFramePerf, DISABLED_HostActiveXInvalidURL) {
  // TODO(stoyan): Create a low integrity level thread && perform the test there
  SimpleModule module;
  AtlAxWinInit();
  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
  wnd.CreateChromeFrameWindow("http://non-existent-domain.org/");
  wnd.CreateControl(true);
  module.RunMessageLoop();
}

TEST_F(ChromeFrameStartupTestActiveX, PerfWarm) {
  RunStartupTest("warm", "t", "about:blank", false /* cold */, 0, NULL,
                 true /* important */, false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfWarm) {
  RunStartupTest("binary_load_warm", "t", "", false /* cold */, 0, NULL,
                 true /* important */, false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfCold) {
  SetConfigInt(L"PreRead", 0);
  base::FilePath binaries_to_evict[] = {
    ffmpegsumo_dll_, chrome_exe_, chrome_dll_, chrome_frame_dll_
  };
  RunStartupTest("cold", "t", "about:blank", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
  DeleteConfigValue(L"PreRead");
}

TEST_F(ChromeFrameStartupTestActiveX, PerfColdPreRead) {
  SetConfigInt(L"PreRead", 1);
  base::FilePath binaries_to_evict[] = {
    ffmpegsumo_dll_, chrome_exe_, chrome_dll_, chrome_frame_dll_
  };
  RunStartupTest("cold_preread", "t", "about:blank", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
  DeleteConfigValue(L"PreRead");
}

TEST_F(ChromeFrameBinariesLoadTest, PerfCold) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  RunStartupTest("binary_load_cold", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPreRead) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadFull;
  RunStartupTest("binary_load_cold_preread", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead15) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 15;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}


TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead25) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 25;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}

TEST_F(ChromeFrameBinariesLoadTest, PerfColdPartialPreRead40) {
  base::FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_};
  pre_read_type_ = kPreReadPartial;
  percentage_to_preread_ = 40;
  RunStartupTest("binary_load_cold_partial_preread", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}

TEST_F(ChromeFrameStartupTestActiveXReference, PerfWarm) {
  RunStartupTest("warm", "t_ref", "about:blank", false /* cold */, 0, NULL,
                 true /* important */, false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationWarm) {
  RunStartupTest("ChromeFrame_init_warm", "t", "", false /* cold */, 0,
                 NULL, true /* important */, false);
}

TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationCold) {
  base::FilePath binaries_to_evict[] = {chrome_frame_dll_};
  RunStartupTest("ChromeFrame_init_cold", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* not important */, false);
}

TEST_F(ChromeFrameStartupTestActiveXReference,
       PerfChromeFrameInitializationWarm) {
  RunStartupTest("ChromeFrame_init_warm", "t_ref", "", false /* cold */, 0,
                 NULL, true /* important */, false);
}

typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTest>
    RegularChromeFrameActiveXMemoryTest;

TEST_F(RegularChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) {
  char *urls[] = {"about:blank"};
  RunTest("memory_about_blank", urls, arraysize(urls));
}

// TODO(iyengar)
// Revisit why the chrome frame dll does not unload correctly when this test is
// run.
// http://code.google.com/p/chromium/issues/detail?id=47812
TEST_F(RegularChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {
  // TODO(iyengar)
  // We should use static pages to measure memory usage.
  char *urls[] = {
    "http://www.youtube.com/watch?v=PN2HAroA12w",
    "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel"
  };

  RunTest("memory", urls, arraysize(urls));
}

typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTestReference>
    ReferenceBuildChromeFrameActiveXMemoryTest;

// Disabled to investigate why the chrome frame dll does not unload while
// running this test.
// http://code.google.com/p/chromium/issues/detail?id=47812
TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest,
       DISABLED_MemoryTestAboutBlank) {
  char *urls[] = {"about:blank"};
  RunTest("memory_about_blank_reference", urls, arraysize(urls));
}

// TODO(iyengar)
// Revisit why the chrome frame dll does not unload correctly when this test is
// run.
TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {
  // TODO(iyengar)
  // We should use static pages to measure memory usage.
  char *urls[] = {
    "http://www.youtube.com/watch?v=PN2HAroA12w",
    "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel"
  };

  RunTest("memory_reference", urls, arraysize(urls));
}

TEST_F(ChromeFrameCreationTest, PerfWarm) {
  RunStartupTest("creation_warm", "t", "", false /* cold */, 0,
                 NULL, true /* important */, false);
}

TEST_F(ChromeFrameCreationTestReference, PerfWarm) {
  RunStartupTest("creation_warm", "t_ref", "about:blank", false /* cold */, 0,
                 NULL, true /* not important */, false);
}

TEST_F(FlashCreationTest, DISABLED_PerfWarm) {
  RunStartupTest("creation_warm", "t_flash", "", false /* cold */, 0, NULL,
                 true /* not important */, false);
}

TEST_F(SilverlightCreationTest, DISABLED_PerfWarm) {
  RunStartupTest("creation_warm", "t_silverlight", "", false /* cold */, 0,
                 NULL, false /* not important */, false);
}

TEST_F(ChromeFrameCreationTest, PerfCold) {
  base::FilePath binaries_to_evict[] = {chrome_frame_dll_};

  RunStartupTest("creation_cold", "t", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 true /* important */, false);
}

// Attempt to evict the Flash control can fail on the buildbot as the dll
// is marked read only. The test run is aborted if we fail to evict the file
// from the cache. This could also fail if the Flash control is in use.
// On Vista this could fail because of UAC
TEST_F(FlashCreationTest, PerfCold) {
  base::win::RegKey flash_key(HKEY_CLASSES_ROOT, kFlashControlKey, KEY_READ);

  std::wstring plugin_path;
  ASSERT_EQ(ERROR_SUCCESS, flash_key.ReadValue(L"", &plugin_path));
  ASSERT_FALSE(plugin_path.empty());

  base::FilePath flash_path = base::FilePath(plugin_path);
  base::FilePath binaries_to_evict[] = {flash_path};

  RunStartupTest("creation_cold", "t_flash", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false/* important */, true);
}

// This test would fail on Vista due to UAC or if the Silverlight control is
// in use. The test run is aborted if we fail to evict the file from the cache.
// Disabling this test as the Silverlight dll does not seem to get unloaded
// correctly causing the attempt to evict the dll from the system cache to
// fail.
TEST_F(SilverlightCreationTest, DISABLED_PerfCold) {
  base::win::RegKey silverlight_key(HKEY_CLASSES_ROOT, kSilverlightControlKey,
                                    KEY_READ);

  std::wstring plugin_path;
  ASSERT_EQ(ERROR_SUCCESS, silverlight_key.ReadValue(L"", &plugin_path));
  ASSERT_FALSE(plugin_path.empty());

  base::FilePath silverlight_path = base::FilePath(plugin_path);
  base::FilePath binaries_to_evict[] = {silverlight_path};

  RunStartupTest("creation_cold", "t_silverlight", "", true /* cold */,
                 arraysize(binaries_to_evict), binaries_to_evict,
                 false /* important */, true);
}

namespace {

// Derive from this class in order to receive custom events traced
// via TRACE_EVENT_XXXX macros from ChromeFrame/Chrome.
class TracedEvents {
 public:
  virtual void OnTraceEventBegin(EVENT_TRACE* event) {}
  virtual void OnTraceEventEnd(EVENT_TRACE* event) {}
  virtual void OnTraceEventInstant(EVENT_TRACE* event) {}
};

// For the time being we pass to delegate only base::kTraceEventClass32
// events i.e. these generated by TRACE_EVENT_XXXX macros.
// We may need to add kernel provider and pass Process Start/Exit events etc,
// but for the time being we stick with base::kChromeTraceProviderName
// provider only.
class EtwConsumer : public base::win::EtwTraceConsumerBase<EtwConsumer> {
 public:
  EtwConsumer() {
    set_delegate(NULL);
  }

  ~EtwConsumer() {
    set_delegate(NULL);
  }

  void set_delegate(TracedEvents* delegate) {
    delegate_ = delegate;
  }

  static void ProcessEvent(EVENT_TRACE* event) {
    DCHECK(delegate_);
    if (event->Header.Guid != base::debug::kTraceEventClass32)
      return;
    if (event->Header.Class.Version != 0)
      return;

    switch (event->Header.Class.Type) {
      case base::debug::kTraceEventTypeBegin:
        delegate_->OnTraceEventBegin(event);
        break;
      case base::debug::kTraceEventTypeEnd:
        delegate_->OnTraceEventEnd(event);
        break;
      case base::debug::kTraceEventTypeInstant:
        delegate_->OnTraceEventInstant(event);
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  static TracedEvents* delegate_;
};

TracedEvents* EtwConsumer::delegate_ = NULL;
};  // namespace

class EtwPerfSession {
 public:
  EtwPerfSession() {
  }

  ~EtwPerfSession() {
    file_util::Delete(etl_log_file_, false);
  }

  void Start() {
    // To ensure there is no session leftover from crashes, previous runs, etc.
    base::win::EtwTraceProperties ignore;
    base::win::EtwTraceController::Stop(L"cf_perf", &ignore);
    ASSERT_TRUE(file_util::CreateTemporaryFile(&etl_log_file_));
    ASSERT_HRESULT_SUCCEEDED(controller_.StartFileSession(L"cf_perf",
        etl_log_file_.value().c_str(), false));
    ASSERT_HRESULT_SUCCEEDED(controller_.EnableProvider(
        base::debug::kChromeTraceProviderName,
        TRACE_LEVEL_INFORMATION,
        ~(base::debug::CAPTURE_STACK_TRACE)));
  }

  HRESULT Stop() {
    return controller_.Stop(NULL);
  }

  void AnalyzeOutput(TracedEvents* delegate) {
    EtwConsumer consumer;
    consumer.set_delegate(delegate);
    consumer.OpenFileSession(etl_log_file_.value().c_str());
    consumer.Consume();
    consumer.Close();
  }

  base::FilePath etl_log_file_;
  base::win::EtwTraceController controller_;
};

// Base class for the tracing event helper classes.
class MonitorTraceBase {
 public:
  static bool IsMatchingEvent(EVENT_TRACE* event,
                              const base::StringPiece& event_to_compare) {
    return event->MofLength > event_to_compare.size() &&
      (memcmp(event_to_compare.data(), event->MofData,
              event_to_compare.size() + 1) == 0);
  }

  bool is_valid() const {
    return !start_.is_null() && !end_.is_null() && start_ <= end_;
  }

  base::TimeDelta duration() const {
    return end_ - start_;
  }

  base::Time start_;
  base::Time end_;
};

// This class measures the time between begin and end events of a particular
// type.
class MonitorTracePair : public MonitorTraceBase,
                         public TracedEvents {
 public:
  MonitorTracePair() : event_(NULL) {
  }

  void set_interesting_event(const char* event) {
    event_ = event;
  }

  virtual void OnTraceEventBegin(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_)) {
      EXPECT_TRUE(start_.is_null());
      start_ = base::Time::FromFileTime(
          reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  virtual void OnTraceEventEnd(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_)) {
      EXPECT_FALSE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      end_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  base::StringPiece event_;
};

// This class measures the time between two events.
class MonitorTraceBetweenEventPair : public MonitorTraceBase,
                                     public TracedEvents {
 public:
  MonitorTraceBetweenEventPair() : event_end_(NULL),
                                   event_start_(NULL) {
  }

  void set_start_event(const char* event) {
    event_start_ = event;
  }

  void set_end_event(const char* event) {
    event_end_ = event;
  }

  virtual void OnTraceEventBegin(EVENT_TRACE* event) {
    if (IsMatchingEvent(event, event_start_)) {
      EXPECT_TRUE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      start_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    } else if (IsMatchingEvent(event, event_end_)) {
      EXPECT_FALSE(start_.is_null());
      EXPECT_TRUE(end_.is_null());
      end_ = base::Time::FromFileTime(
        reinterpret_cast<FILETIME&>(event->Header.TimeStamp));
    }
  }

  virtual void OnTraceEventEnd(EVENT_TRACE* event) {}

  base::StringPiece event_start_;
  base::StringPiece event_end_;
};

// The very same as UIPerfTest::PrintResultXXXX without the need to
// create an UIPerfTest instance.
void PrintResultsImpl(const std::string& measurement,
                      const std::string& modifier,
                      const std::string& trace,
                      const std::string& values,
                      const std::string& prefix,
                      const std::string& suffix,
                      const std::string& units,
                      bool important) {
  // <*>RESULT <graph_name>: <trace_name>= <value> <units>
  // <*>RESULT <graph_name>: <trace_name>= {<mean>, <std deviation>} <units>
  // <*>RESULT <graph_name>: <trace_name>= [<value>,value,value,...,] <units>
  printf("%sRESULT %s%s: %s= %s%s%s %s\n",
         important ? "*" : "", measurement.c_str(), modifier.c_str(),
         trace.c_str(), prefix.c_str(), values.c_str(), suffix.c_str(),
         units.c_str());
}

void PrintResultList(const std::string& measurement,
                     const std::string& modifier,
                     const std::string& trace,
                     const std::string& values,
                     const std::string& units,
                     bool important) {
  PrintResultsImpl(measurement, modifier, trace, values,
      "[", "]", units, important);
}

bool RunSingleTestOutOfProc(const std::string& test_name) {
  base::FilePath path;
  if (!PathService::Get(base::DIR_EXE, &path))
    return false;
  path = path.Append(L"chrome_frame_tests.exe");

  CommandLine cmd_line(path);
  // Always enable disabled tests.  This method is not called with disabled
  // tests unless this flag was specified to the browser test executable.
  cmd_line.AppendSwitch("gtest_also_run_disabled_tests");
  cmd_line.AppendSwitchASCII("gtest_filter", test_name);

  base::ProcessHandle process_handle;
  if (!base::LaunchProcess(cmd_line, base::LaunchOptions(), &process_handle))
    return false;

  base::TimeDelta test_terminate_timeout = base::TimeDelta::FromMinutes(1);
  int exit_code = 0;
  if (!base::WaitForExitCodeWithTimeout(process_handle, &exit_code,
                                        test_terminate_timeout)) {
    LOG(ERROR) << "Test timeout (" << test_terminate_timeout.InMilliseconds()
               << " ms) exceeded for " << test_name;

    exit_code = -1;  // Set a non-zero exit code to signal a failure.

    // Ensure that the process terminates.
    base::KillProcess(process_handle, -1, true);
  }

  base::CloseProcessHandle(process_handle);

  return exit_code == 0;
}

template <class Monitor>
void PrintPerfTestResults(const Monitor* monitor,
                          int num_cycles,
                          const char* result_name) {
  std::string times;

  for (int i = 0; i < num_cycles; ++i) {
    ASSERT_TRUE(monitor[i].is_valid());
    base::StringAppendF(&times,
                        "%.2f,",
                        monitor[i].duration().InMillisecondsF());
  }

  PrintResultList(result_name, "", "t", times, "ms", false);
}

TEST(TestAsPerfTest, MetaTag_createproxy) {
  const int kNumCycles = 10;

  MonitorTracePair create_proxy_monitor[kNumCycles];
  MonitorTraceBetweenEventPair browser_main_start_monitor[kNumCycles];
  MonitorTraceBetweenEventPair browser_main_loop_monitor[kNumCycles];
  MonitorTraceBetweenEventPair automation_provider_start_monitor[kNumCycles];
  MonitorTraceBetweenEventPair automation_provider_connect_monitor[kNumCycles];
  MonitorTracePair external_tab_navigate_monitor[kNumCycles];
  MonitorTracePair pre_read_chrome_monitor[kNumCycles];
  MonitorTraceBetweenEventPair renderer_main_monitor[kNumCycles];

  for (int i = 0; i < kNumCycles; ++i) {
    EtwPerfSession perf_session;
    ASSERT_NO_FATAL_FAILURE(perf_session.Start());
    ASSERT_TRUE(RunSingleTestOutOfProc(
        "ChromeFrameTestWithWebServer.FullTabModeIE_MetaTag"));
    // Since we cannot have array of objects with a non-default constructor,
    // dedicated method is used to initialize watched event.
    create_proxy_monitor[i].set_interesting_event("chromeframe.createproxy");

    browser_main_start_monitor[i].set_start_event("chromeframe.createproxy");
    browser_main_start_monitor[i].set_end_event("BrowserMain");

    browser_main_loop_monitor[i].set_start_event("BrowserMain");
    browser_main_loop_monitor[i].set_end_event("BrowserMain:MESSAGE_LOOP");

    automation_provider_start_monitor[i].set_start_event("BrowserMain");
    automation_provider_start_monitor[i].set_end_event(
        "AutomationProvider::AutomationProvider");

    automation_provider_connect_monitor[i].set_start_event(
        "AutomationProvider::AutomationProvider");
    automation_provider_connect_monitor[i].set_end_event(
        "AutomationProvider::InitializeChannel");

    external_tab_navigate_monitor[i].set_interesting_event(
        "ExternalTabContainerWin::Navigate");

    renderer_main_monitor[i].set_start_event(
        "ExternalTabContainerWin::Navigate");
    renderer_main_monitor[i].set_end_event("RendererMain");

    pre_read_chrome_monitor[i].set_interesting_event("PreReadImage");

    ASSERT_HRESULT_SUCCEEDED(perf_session.Stop());

    perf_session.AnalyzeOutput(&create_proxy_monitor[i]);
    perf_session.AnalyzeOutput(&browser_main_start_monitor[i]);
    perf_session.AnalyzeOutput(&browser_main_loop_monitor[i]);
    perf_session.AnalyzeOutput(&automation_provider_start_monitor[i]);
    perf_session.AnalyzeOutput(&automation_provider_connect_monitor[i]);
    perf_session.AnalyzeOutput(&external_tab_navigate_monitor[i]);
    perf_session.AnalyzeOutput(&pre_read_chrome_monitor[i]);
    perf_session.AnalyzeOutput(&renderer_main_monitor[i]);
  }

  // Print results
  PrintPerfTestResults(create_proxy_monitor, kNumCycles, "createproxy");
  PrintPerfTestResults(browser_main_start_monitor, kNumCycles,
                       "browserstart");
  PrintPerfTestResults(browser_main_loop_monitor, kNumCycles,
                       "browserloop");
  PrintPerfTestResults(automation_provider_start_monitor, kNumCycles,
                       "automationproviderstart");
  PrintPerfTestResults(automation_provider_connect_monitor, kNumCycles,
                       "automationproviderconnect");
  PrintPerfTestResults(external_tab_navigate_monitor, kNumCycles,
                       "externaltabnavigate");
  PrintPerfTestResults(renderer_main_monitor, kNumCycles,
                       "beginrenderermain");
#ifdef NDEBUG
  PrintPerfTestResults(pre_read_chrome_monitor, kNumCycles, "PreReadImage");
#endif  // NDEBUG
}
