168 lines
5.3 KiB
C++
168 lines
5.3 KiB
C++
// 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) {
|
|
// TODO(b/128871825,golang.org/issue/10958): Test times out when there is a
|
|
// small number of core because one goroutine starves the others.
|
|
printf("CPUS: %d\n", std::thread::hardware_concurrency());
|
|
SKIP_IF(std::thread::hardware_concurrency() <= 2);
|
|
|
|
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
|