/*
 * libjingle
 * Copyright 2010 Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <iomanip>
#include <iostream>
#include <vector>

#ifdef WIN32
#include "talk/base/win32.h"
#endif

#include "talk/base/cpumonitor.h"
#include "talk/base/flags.h"
#include "talk/base/gunit.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/thread.h"
#include "talk/base/timeutils.h"
#include "talk/base/timing.h"

namespace talk_base {

static const int kMaxCpus = 1024;
static const int kSettleTime = 100;  // Amount of time to between tests.
static const int kIdleTime = 500;  // Amount of time to be idle in ms.
static const int kBusyTime = 1000;  // Amount of time to be busy in ms.
static const int kLongInterval = 2000;  // Interval longer than busy times

class BusyThread : public talk_base::Thread {
 public:
  BusyThread(double load, double duration, double interval) :
    load_(load), duration_(duration), interval_(interval) {
  }
  void Run() {
    Timing time;
    double busy_time = interval_ * load_ / 100.0;
    for (;;) {
      time.BusyWait(busy_time);
      time.IdleWait(interval_ - busy_time);
      if (duration_) {
        duration_ -= interval_;
        if (duration_ <= 0) {
          break;
        }
      }
    }
  }
 private:
  double load_;
  double duration_;
  double interval_;
};

class CpuLoadListener : public sigslot::has_slots<> {
 public:
  CpuLoadListener()
      : current_cpus_(0),
        cpus_(0),
        process_load_(.0f),
        system_load_(.0f),
        count_(0) {
  }

  void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) {
    current_cpus_ = current_cpus;
    cpus_ = cpus;
    process_load_ = proc_load;
    system_load_ = sys_load;
    ++count_;
  }

  int current_cpus() const { return current_cpus_; }
  int cpus() const { return cpus_; }
  float process_load() const { return process_load_; }
  float system_load() const { return system_load_; }
  int count() const { return count_; }

 private:
  int current_cpus_;
  int cpus_;
  float process_load_;
  float system_load_;
  int count_;
};

// Set affinity (which cpu to run on), but respecting FLAG_affinity:
// -1 means no affinity - run on whatever cpu is available.
// 0 .. N means run on specific cpu.  The tool will create N threads and call
//   SetThreadAffinity on 0 to N - 1 as cpu.  FLAG_affinity sets the first cpu
//   so the range becomes affinity to affinity + N - 1
// Note that this function affects Windows scheduling, effectively giving
//   the thread with affinity for a specified CPU more priority on that CPU.
bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) {
#ifdef WIN32
  if (affinity >= 0) {
    return ::SetThreadAffinityMask(t->GetHandle(),
        1 << (cpu + affinity)) != FALSE;
  }
#endif
  return true;
}

bool SetThreadPriority(BusyThread* t, int prio) {
  if (!prio) {
    return true;
  }
  bool ok = t->SetPriority(static_cast<talk_base::ThreadPriority>(prio));
  if (!ok) {
    std::cout << "Error setting thread priority." << std::endl;
  }
  return ok;
}

int CpuLoad(double cpuload, double duration, int numthreads,
            int priority, double interval, int affinity) {
  int ret = 0;
  std::vector<BusyThread*> threads;
  for (int i = 0; i < numthreads; ++i) {
    threads.push_back(new BusyThread(cpuload, duration, interval));
    // NOTE(fbarchard): Priority must be done before Start.
    if (!SetThreadPriority(threads[i], priority) ||
       !threads[i]->Start() ||
       !SetThreadAffinity(threads[i], i, affinity)) {
      ret = 1;
      break;
    }
  }
  // Wait on each thread
  if (ret == 0) {
    for (int i = 0; i < numthreads; ++i) {
      threads[i]->Stop();
    }
  }

  for (int i = 0; i < numthreads; ++i) {
    delete threads[i];
  }
  return ret;
}

// Make 2 CPUs busy
static void CpuTwoBusyLoop(int busytime) {
  CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1);
}

// Make 1 CPUs busy
static void CpuBusyLoop(int busytime) {
  CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1);
}

// Make 1 use half CPU time.
static void CpuHalfBusyLoop(int busytime) {
  CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1);
}

void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) {
  CpuSampler sampler;
  sampler.set_force_fallback(force_fallback);
  EXPECT_TRUE(sampler.Init());
  sampler.set_load_interval(100);
  int cpus = sampler.GetMaxCpus();

  // Test1: CpuSampler under idle situation.
  Thread::SleepMs(kSettleTime);
  sampler.GetProcessLoad();
  sampler.GetSystemLoad();

  Thread::SleepMs(kIdleTime);

  float proc_idle = 0.f, sys_idle = 0.f;
  if (test_proc) {
    proc_idle = sampler.GetProcessLoad();
  }
  if (test_sys) {
      sys_idle = sampler.GetSystemLoad();
  }
  if (test_proc) {
    LOG(LS_INFO) << "ProcessLoad Idle:      "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << proc_idle;
    EXPECT_GE(proc_idle, 0.f);
    EXPECT_LE(proc_idle, static_cast<float>(cpus));
  }
  if (test_sys) {
    LOG(LS_INFO) << "SystemLoad Idle:       "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << sys_idle;
    EXPECT_GE(sys_idle, 0.f);
    EXPECT_LE(sys_idle, static_cast<float>(cpus));
  }

  // Test2: CpuSampler with main process at 50% busy.
  Thread::SleepMs(kSettleTime);
  sampler.GetProcessLoad();
  sampler.GetSystemLoad();

  CpuHalfBusyLoop(kBusyTime);

  float proc_halfbusy = 0.f, sys_halfbusy = 0.f;
  if (test_proc) {
    proc_halfbusy = sampler.GetProcessLoad();
  }
  if (test_sys) {
    sys_halfbusy = sampler.GetSystemLoad();
  }
  if (test_proc) {
    LOG(LS_INFO) << "ProcessLoad Halfbusy:  "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << proc_halfbusy;
    EXPECT_GE(proc_halfbusy, 0.f);
    EXPECT_LE(proc_halfbusy, static_cast<float>(cpus));
  }
  if (test_sys) {
    LOG(LS_INFO) << "SystemLoad Halfbusy:   "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << sys_halfbusy;
    EXPECT_GE(sys_halfbusy, 0.f);
    EXPECT_LE(sys_halfbusy, static_cast<float>(cpus));
  }

  // Test3: CpuSampler with main process busy.
  Thread::SleepMs(kSettleTime);
  sampler.GetProcessLoad();
  sampler.GetSystemLoad();

  CpuBusyLoop(kBusyTime);

  float proc_busy = 0.f, sys_busy = 0.f;
  if (test_proc) {
    proc_busy = sampler.GetProcessLoad();
  }
  if (test_sys) {
    sys_busy = sampler.GetSystemLoad();
  }
  if (test_proc) {
    LOG(LS_INFO) << "ProcessLoad Busy:      "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << proc_busy;
    EXPECT_GE(proc_busy, 0.f);
    EXPECT_LE(proc_busy, static_cast<float>(cpus));
  }
  if (test_sys) {
    LOG(LS_INFO) << "SystemLoad Busy:       "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << sys_busy;
    EXPECT_GE(sys_busy, 0.f);
    EXPECT_LE(sys_busy, static_cast<float>(cpus));
  }

  // Test4: CpuSampler with 2 cpus process busy.
  if (cpus >= 2) {
    Thread::SleepMs(kSettleTime);
    sampler.GetProcessLoad();
    sampler.GetSystemLoad();

    CpuTwoBusyLoop(kBusyTime);

    float proc_twobusy = 0.f, sys_twobusy = 0.f;
    if (test_proc) {
      proc_twobusy = sampler.GetProcessLoad();
    }
    if (test_sys) {
      sys_twobusy = sampler.GetSystemLoad();
    }
    if (test_proc) {
      LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:"
                   << setiosflags(std::ios_base::fixed)
                   << std::setprecision(2) << std::setw(6) << proc_twobusy;
      EXPECT_GE(proc_twobusy, 0.f);
      EXPECT_LE(proc_twobusy, static_cast<float>(cpus));
    }
    if (test_sys) {
      LOG(LS_INFO) << "SystemLoad 2 CPU Busy: "
                   << setiosflags(std::ios_base::fixed)
                   << std::setprecision(2) << std::setw(6) << sys_twobusy;
      EXPECT_GE(sys_twobusy, 0.f);
      EXPECT_LE(sys_twobusy, static_cast<float>(cpus));
    }
  }

  // Test5: CpuSampler with idle process after being busy.
  Thread::SleepMs(kSettleTime);
  sampler.GetProcessLoad();
  sampler.GetSystemLoad();

  Thread::SleepMs(kIdleTime);

  if (test_proc) {
    proc_idle = sampler.GetProcessLoad();
  }
  if (test_sys) {
    sys_idle = sampler.GetSystemLoad();
  }
  if (test_proc) {
    LOG(LS_INFO) << "ProcessLoad Idle:      "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << proc_idle;
    EXPECT_GE(proc_idle, 0.f);
    EXPECT_LE(proc_idle, proc_busy);
  }
  if (test_sys) {
    LOG(LS_INFO) << "SystemLoad Idle:       "
                 << setiosflags(std::ios_base::fixed)
                 << std::setprecision(2) << std::setw(6) << sys_idle;
    EXPECT_GE(sys_idle, 0.f);
    EXPECT_LE(sys_idle, static_cast<float>(cpus));
  }
}

TEST(CpuMonitorTest, TestCpus) {
  CpuSampler sampler;
  EXPECT_TRUE(sampler.Init());
  int current_cpus = sampler.GetCurrentCpus();
  int cpus = sampler.GetMaxCpus();
  LOG(LS_INFO) << "Current Cpus:     " << std::setw(9) << current_cpus;
  LOG(LS_INFO) << "Maximum Cpus:     " << std::setw(9) << cpus;
  EXPECT_GT(cpus, 0);
  EXPECT_LE(cpus, kMaxCpus);
  EXPECT_GT(current_cpus, 0);
  EXPECT_LE(current_cpus, cpus);
}

#ifdef WIN32
// Tests overall system CpuSampler using legacy OS fallback code if applicable.
TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) {
  TestCpuSampler(false, true, true);
}
#endif

// Tests both process and system functions in use at same time.
TEST(CpuMonitorTest, TestGetBothLoad) {
  TestCpuSampler(true, true, false);
}

// Tests a query less than the interval produces the same value.
TEST(CpuMonitorTest, TestInterval) {
  CpuSampler sampler;
  EXPECT_TRUE(sampler.Init());

  // Test1: Set interval to large value so sampler will not update.
  sampler.set_load_interval(kLongInterval);

  sampler.GetProcessLoad();
  sampler.GetSystemLoad();

  float proc_orig = sampler.GetProcessLoad();
  float sys_orig = sampler.GetSystemLoad();

  Thread::SleepMs(kIdleTime);

  float proc_halftime = sampler.GetProcessLoad();
  float sys_halftime = sampler.GetSystemLoad();

  EXPECT_EQ(proc_orig, proc_halftime);
  EXPECT_EQ(sys_orig, sys_halftime);
}

TEST(CpuMonitorTest, TestCpuMonitor) {
  CpuMonitor monitor(Thread::Current());
  CpuLoadListener listener;
  monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad);
  EXPECT_TRUE(monitor.Start(10));
  Thread::Current()->ProcessMessages(50);
  EXPECT_GT(listener.count(), 2);  // We have checked cpu load more than twice.
  EXPECT_GT(listener.current_cpus(), 0);
  EXPECT_GT(listener.cpus(), 0);
  EXPECT_GE(listener.process_load(), .0f);
  EXPECT_GE(listener.system_load(), .0f);

  monitor.Stop();
  // Wait 20 ms to ake sure all signals are delivered.
  Thread::Current()->ProcessMessages(20);
  int old_count = listener.count();
  Thread::Current()->ProcessMessages(20);
  // Verfy no more siganls.
  EXPECT_EQ(old_count, listener.count());
}

}  // namespace talk_base
