/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/io/async/test/TimeUtil.h>

#include <thread>

#include <glog/logging.h>

#include <folly/portability/GTest.h>
#include <folly/portability/Unistd.h>

using folly::TimePoint;
using namespace std::literals::chrono_literals;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using std::chrono::steady_clock;

// Define a PrintTo() function for std::chrono::nanoseconds so that these
// will be printed nicely on EXPECT*() failures.
// Define this in std::chrono so that argument-dependent lookup works.
namespace std {
namespace chrono {
static inline void PrintTo(nanoseconds ns, ::std::ostream* os) {
  *os << ns.count() << "ns";
}
} // namespace chrono
} // namespace std

#ifdef __linux__
void runThread(nanoseconds duration, nanoseconds* timeWaiting) {
  TimePoint start;

  // Loop consuming CPU until the duration has expired.
  while (true) {
    TimePoint now;
    if (now.getTimeStart() - start.getTimeStart() > duration) {
      // Time to quit
      // Report how long we spent waiting to be scheduled on the CPU.
      *timeWaiting = (now.getTimeWaiting() - start.getTimeWaiting());
      VLOG(1) << "thread " << start.getTid() << ": elapsed "
              << duration_cast<milliseconds>(
                     now.getTimeStart() - start.getTimeStart())
                     .count()
              << "ms, time waiting: "
              << duration_cast<milliseconds>(*timeWaiting).count() << "ms";
      break;
    }
  }
}

// Test to make sure that TimePoint computes sane values for time
// spent waiting on CPU.
TEST(TimeUtil, getTimeWaiting) {
  TimePoint tp;

  // Run twice as many threads as CPU cores, to ensure that some of
  // them should be waiting sometime.
  auto numThreads = sysconf(_SC_NPROCESSORS_CONF) * 2;

  std::vector<std::thread> threads;
  std::vector<nanoseconds> timeWaiting;
  timeWaiting.resize(numThreads, 0ns);

  auto start = steady_clock::now();
  for (int n = 0; n < numThreads; ++n) {
    threads.emplace_back(runThread, 1s, &timeWaiting[n]);
  }

  for (auto& thread : threads) {
    thread.join();
  }
  auto end = steady_clock::now();

  auto timeSpent = end - start;
  nanoseconds max{0};
  for (int n = 0; n < numThreads; ++n) {
    max = std::max(max, timeWaiting[n]);
    // No thread could possibly have been waiting for longer than
    // the test actually took to run.
    EXPECT_LT(timeWaiting[n], timeSpent);
  }
  // Make sure that at least one thread spent some time waiting
  EXPECT_GE(max, 1ns);
}
#endif
