// Copyright 2018 The gVisor Authors.
//
// 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 <pthread.h>
#include <sys/time.h>

#include <cerrno>
#include <cstdint>
#include <ctime>
#include <list>
#include <memory>
#include <string>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"

namespace gvisor {
namespace testing {

namespace {

int64_t clock_gettime_nsecs(clockid_t id) {
  struct timespec ts;
  TEST_PCHECK(clock_gettime(id, &ts) == 0);
  return (ts.tv_sec * 1000000000 + ts.tv_nsec);
}

// Spin on the CPU for at least ns nanoseconds, based on
// CLOCK_THREAD_CPUTIME_ID.
void spin_ns(int64_t ns) {
  int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID);
  int64_t end = start + ns;

  do {
    constexpr int kLoopCount = 1000000;  // large and arbitrary
    // volatile to prevent the compiler from skipping this loop.
    for (volatile int i = 0; i < kLoopCount; i++) {
    }
  } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end);
}

// Test that CLOCK_PROCESS_CPUTIME_ID is a superset of CLOCK_THREAD_CPUTIME_ID.
TEST(ClockGettime, CputimeId) {
  constexpr int kNumThreads = 13;  // arbitrary

  absl::Duration spin_time = absl::Seconds(1);

  // Start off the worker threads and compute the aggregate time spent by
  // the workers. Note that we test CLOCK_PROCESS_CPUTIME_ID by having the
  // workers execute in parallel and verifying that CLOCK_PROCESS_CPUTIME_ID
  // accumulates the runtime of all threads.
  int64_t start = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID);

  // Create a kNumThreads threads.
  std::list<ScopedThread> threads;
  for (int i = 0; i < kNumThreads; i++) {
    threads.emplace_back(
        [spin_time] { spin_ns(absl::ToInt64Nanoseconds(spin_time)); });
  }
  for (auto& t : threads) {
    t.Join();
  }

  int64_t end = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID);

  // The aggregate time spent in the worker threads must be at least
  // 'kNumThreads' times the time each thread spun.
  ASSERT_GE(end - start, kNumThreads * absl::ToInt64Nanoseconds(spin_time));
}

TEST(ClockGettime, JavaThreadTime) {
  clockid_t clockid;
  ASSERT_EQ(0, pthread_getcpuclockid(pthread_self(), &clockid));
  struct timespec tp;
  ASSERT_THAT(clock_getres(clockid, &tp), SyscallSucceeds());
  EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0);
  // A thread cputime is updated each 10msec and there is no approximation
  // if a task is running.
  do {
    ASSERT_THAT(clock_gettime(clockid, &tp), SyscallSucceeds());
  } while (tp.tv_sec == 0 && tp.tv_nsec == 0);
  EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0);
}

// There is not much to test here, since CLOCK_REALTIME may be discontiguous.
TEST(ClockGettime, RealtimeWorks) {
  struct timespec tp;
  EXPECT_THAT(clock_gettime(CLOCK_REALTIME, &tp), SyscallSucceeds());
}

class MonotonicClockTest : public ::testing::TestWithParam<clockid_t> {};

TEST_P(MonotonicClockTest, IsMonotonic) {
  auto end = absl::Now() + absl::Seconds(5);

  struct timespec tp;
  EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds());

  auto prev = absl::TimeFromTimespec(tp);
  while (absl::Now() < end) {
    EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds());
    auto now = absl::TimeFromTimespec(tp);
    EXPECT_GE(now, prev);
    prev = now;
  }
}

std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) {
  switch (info.param) {
    case CLOCK_MONOTONIC:
      return "CLOCK_MONOTONIC";
    case CLOCK_MONOTONIC_COARSE:
      return "CLOCK_MONOTONIC_COARSE";
    case CLOCK_MONOTONIC_RAW:
      return "CLOCK_MONOTONIC_RAW";
    case CLOCK_BOOTTIME:
      // CLOCK_BOOTTIME is a monotonic clock.
      return "CLOCK_BOOTTIME";
    default:
      return absl::StrCat(info.param);
  }
}

INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicClockTest,
                         ::testing::Values(CLOCK_MONOTONIC,
                                           CLOCK_MONOTONIC_COARSE,
                                           CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME),
                         PrintClockId);

TEST(ClockGettime, UnimplementedReturnsEINVAL) {
  SKIP_IF(!IsRunningOnGvisor());

  struct timespec tp;
  EXPECT_THAT(clock_gettime(CLOCK_REALTIME_ALARM, &tp),
              SyscallFailsWithErrno(EINVAL));
  EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME_ALARM, &tp),
              SyscallFailsWithErrno(EINVAL));
}

TEST(ClockGettime, InvalidClockIDReturnsEINVAL) {
  struct timespec tp;
  EXPECT_THAT(clock_gettime(-1, &tp), SyscallFailsWithErrno(EINVAL));
}

}  // namespace

}  // namespace testing
}  // namespace gvisor
