// Copyright 2016 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 "stdafx.h"

#include <algorithm>
#include <map>
#include <vector>

#include "power_sampler.h"
#include "system_information_sampler.h"

// Result data structure contains a final set of values calculated based on
// comparison of two snapshots. These are the values that the tool prints
// in the output.
struct Result {
  ULONG idle_wakeups_per_sec;
  double cpu_usage;
  ULONGLONG working_set;
  double power;
};

typedef std::vector<Result> ResultVector;

// The following 4 functions are used for sorting of ResultVector.
ULONG GetIdleWakeupsPerSec(const Result& r) {
  return r.idle_wakeups_per_sec;
}
double GetCpuUsage(const Result& r) {
  return r.cpu_usage;
}
ULONGLONG GetWorkingSet(const Result& r) {
  return r.working_set;
}
double GetPower(const Result& r) {
  return r.power;
}

template <typename T>
T GetMedian(ResultVector* results, T (*getter)(const Result&)) {
  std::sort(results->begin(), results->end(),
            [&](const Result& lhs, const Result& rhs) {
              return getter(lhs) < getter(rhs);
            });

  size_t median_index = results->size() / 2;
  if (results->size() % 2 != 0) {
    return getter((*results)[median_index]);
  } else {
    return (getter((*results)[median_index - 1]) +
            getter((*results)[median_index])) /
           2;
  }
}

// This class holds the app state and constains a number of utilities for
// collecting and diffing snapshots of data, handling processes, etc.
class IdleWakeups {
 public:
  IdleWakeups();
  ~IdleWakeups();

  Result DiffSnapshots(const ProcessDataSnapshot& prev_snapshot,
                       const ProcessDataSnapshot& snapshot);

  void OpenProcesses(const ProcessDataSnapshot& snapshot);
  void CloseProcesses();

 private:
  HANDLE GetProcessHandle(ProcessId process_id);
  void OpenProcess(ProcessId process_id);
  void CloseProcess(ProcessId process_id);
  bool GetFinishedProcessCpuTime(ProcessId process_id, ULONGLONG* cpu_usage);

  static ULONG CountContextSwitches(const ProcessData& process_data);
  static ULONG DiffContextSwitches(const ProcessData& prev_process_data,
                                   const ProcessData& process_data);

  std::map<ProcessId, HANDLE> process_id_to_hanle_map;

  IdleWakeups& operator=(const IdleWakeups&) = delete;
  IdleWakeups(const IdleWakeups&) = delete;
};

IdleWakeups::IdleWakeups() {}

IdleWakeups::~IdleWakeups() {
  CloseProcesses();
}

void IdleWakeups::OpenProcesses(const ProcessDataSnapshot& snapshot) {
  for (auto& pair : snapshot.processes) {
    OpenProcess(pair.first);
  }
}

void IdleWakeups::CloseProcesses() {
  for (auto& pair : process_id_to_hanle_map) {
    CloseHandle(pair.second);
  }
  process_id_to_hanle_map.clear();
}

HANDLE IdleWakeups::GetProcessHandle(ProcessId process_id) {
  return process_id_to_hanle_map[process_id];
}

void IdleWakeups::OpenProcess(ProcessId process_id) {
  process_id_to_hanle_map[process_id] = ::OpenProcess(
      PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)(ULONGLONG)process_id);
}

void IdleWakeups::CloseProcess(ProcessId process_id) {
  HANDLE handle = GetProcessHandle(process_id);
  CloseHandle(handle);
  process_id_to_hanle_map.erase(process_id);
}

ULONG IdleWakeups::CountContextSwitches(const ProcessData& process_data) {
  ULONG context_switches = 0;

  for (const auto& thread_data : process_data.threads) {
    context_switches += thread_data.context_switches;
  }

  return context_switches;
}

ULONG IdleWakeups::DiffContextSwitches(const ProcessData& prev_process_data,
                                       const ProcessData& process_data) {
  ULONG context_switches = 0;
  size_t prev_index = 0;

  for (const auto& thread_data : process_data.threads) {
    ULONG prev_context_switches = 0;

    for (; prev_index < prev_process_data.threads.size(); ++prev_index) {
      const auto& prev_thread_data = prev_process_data.threads[prev_index];
      if (prev_thread_data.thread_id == thread_data.thread_id) {
        prev_context_switches = prev_thread_data.context_switches;
        ++prev_index;
        break;
      }

      if (prev_thread_data.thread_id > thread_data.thread_id)
        break;
    }

    context_switches += thread_data.context_switches - prev_context_switches;
  }

  return context_switches;
}

bool IdleWakeups::GetFinishedProcessCpuTime(ProcessId process_id,
                                            ULONGLONG* cpu_time) {
  HANDLE process_handle = GetProcessHandle(process_id);

  FILETIME creation_time, exit_time, kernel_time, user_time;
  if (GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time,
                      &user_time)) {
    ULARGE_INTEGER ul_kernel_time, ul_user_time;
    ul_kernel_time.LowPart = kernel_time.dwLowDateTime;
    ul_kernel_time.HighPart = kernel_time.dwHighDateTime;
    ul_user_time.LowPart = user_time.dwLowDateTime;
    ul_user_time.HighPart = user_time.dwHighDateTime;
    *cpu_time = ul_kernel_time.QuadPart + ul_user_time.QuadPart;
    return true;
  }

  *cpu_time = 0;
  return false;
}

Result IdleWakeups::DiffSnapshots(const ProcessDataSnapshot& prev_snapshot,
                                  const ProcessDataSnapshot& snapshot) {
  ULONG idle_wakeups_delta = 0;
  ULONGLONG cpu_usage_delta = 0;
  ULONGLONG total_working_set = 0;

  ProcessDataMap::const_iterator prev_it = prev_snapshot.processes.begin();

  for (const auto& it : snapshot.processes) {
    ProcessId process_id = it.first;
    const ProcessData& process_data = it.second;
    const ProcessData* prev_process_data_to_diff = nullptr;
    ULONGLONG prev_process_cpu_time = 0;

    for (; prev_it != prev_snapshot.processes.end(); ++prev_it) {
      ProcessId prev_process_id = prev_it->first;
      const ProcessData& prev_process_data = prev_it->second;

      if (prev_process_id == process_id) {
        prev_process_data_to_diff = &prev_process_data;
        prev_process_cpu_time = prev_process_data.cpu_time;
        ++prev_it;
        break;
      }

      if (prev_process_id > process_id)
        break;

      // Prev process disappeared.
      ULONGLONG last_known_cpu_time;
      if (GetFinishedProcessCpuTime(prev_process_id, &last_known_cpu_time)) {
        cpu_usage_delta += last_known_cpu_time - prev_process_data.cpu_time;
      }
      CloseProcess(prev_process_id);
    }

    if (prev_process_data_to_diff) {
      idle_wakeups_delta +=
          DiffContextSwitches(*prev_process_data_to_diff, process_data);
    } else {
      // New process that we haven't seen before.
      OpenProcess(process_id);
      idle_wakeups_delta += CountContextSwitches(process_data);
    }

    cpu_usage_delta += process_data.cpu_time - prev_process_cpu_time;
    total_working_set += process_data.working_set / 1024;
  }

  double time_delta = snapshot.timestamp - prev_snapshot.timestamp;
  Result result;
  result.idle_wakeups_per_sec =
      static_cast<ULONG>(idle_wakeups_delta / time_delta);
  // brucedawson: Don't divide by number of processors so that all numbers are
  // percentage of a core
  // result.cpu_usage = (double)cpu_usage_delta * 100 / (time_delta * 10000000 *
  // NumberOfprocessors());
  result.cpu_usage = (double)cpu_usage_delta * 100 / (time_delta * 10000000);
  result.working_set = total_working_set;

  return result;
}

HANDLE ctrl_c_pressed = NULL;

BOOL WINAPI HandlerFunction(DWORD ctrl_type) {
  if (ctrl_type == CTRL_C_EVENT) {
    printf("Ctrl+C pressed...\n");
    SetEvent(ctrl_c_pressed);
    return TRUE;
  }

  return FALSE;
}

const DWORD sleep_time_sec = 2;

void PrintHeader() {
  printf(
      "------------------------------------------------------------------------"
      "----------\n");
  printf(
      "                       Context switches/sec    CPU usage    Working set "
      "     Power\n");
  printf(
      "------------------------------------------------------------------------"
      "----------\n");
}

#define RESULT_FORMAT_STRING "    %20lu    %8.2f%c    %6.2f MiB    %4.2f W\n"

int wmain(int argc, wchar_t* argv[]) {
  ctrl_c_pressed = CreateEvent(NULL, FALSE, FALSE, NULL);
  SetConsoleCtrlHandler(HandlerFunction, TRUE);

  PowerSampler power_sampler;
  SystemInformationSampler system_information_sampler(argc > 1 ? argv[1]
                                                               : L"chrome.exe");
  IdleWakeups the_app;

  // Take the initial snapshot.
  std::unique_ptr<ProcessDataSnapshot> previous_snapshot =
      system_information_sampler.TakeSnapshot();

  the_app.OpenProcesses(*previous_snapshot);

  ULONG cumulative_idle_wakeups_per_sec = 0;
  double cumulative_cpu_usage = 0.0;
  ULONGLONG cumulative_working_set = 0;
  double cumulative_energy = 0.0;

  ResultVector results;

  printf("Capturing perf data for all processes matching %ls\n",
         system_information_sampler.target_process_name_filter());

  PrintHeader();

  for (;;) {
    if (WaitForSingleObject(ctrl_c_pressed, sleep_time_sec * 1000) ==
        WAIT_OBJECT_0)
      break;

    std::unique_ptr<ProcessDataSnapshot> snapshot =
        system_information_sampler.TakeSnapshot();
    size_t number_of_processes = snapshot->processes.size();

    Result result = the_app.DiffSnapshots(*previous_snapshot, *snapshot);
    previous_snapshot = std::move(snapshot);

    power_sampler.SampleCPUPowerState();
    result.power = power_sampler.get_power(L"Processor");

    printf("%9u processes" RESULT_FORMAT_STRING, (DWORD)number_of_processes,
           result.idle_wakeups_per_sec, result.cpu_usage, '%',
           result.working_set / 1024.0, result.power);

    cumulative_idle_wakeups_per_sec += result.idle_wakeups_per_sec;
    cumulative_cpu_usage += result.cpu_usage;
    cumulative_working_set += result.working_set;
    cumulative_energy += result.power;

    results.push_back(result);
  }

  CloseHandle(ctrl_c_pressed);

  ULONG sample_count = (ULONG)results.size();
  if (sample_count == 0)
    return 0;

  PrintHeader();

  printf("            Average" RESULT_FORMAT_STRING,
         cumulative_idle_wakeups_per_sec / sample_count,
         cumulative_cpu_usage / sample_count, '%',
         (cumulative_working_set / 1024.0) / sample_count,
         cumulative_energy / sample_count);

  Result median_result;

  median_result.idle_wakeups_per_sec =
      GetMedian<ULONG>(&results, GetIdleWakeupsPerSec);
  median_result.cpu_usage = GetMedian<double>(&results, GetCpuUsage);
  median_result.working_set = GetMedian<ULONGLONG>(&results, GetWorkingSet);
  median_result.power = GetMedian<double>(&results, GetPower);

  printf("             Median" RESULT_FORMAT_STRING,
         median_result.idle_wakeups_per_sec, median_result.cpu_usage, '%',
         median_result.working_set / 1024.0, median_result.power);

  return 0;
}
