diff options
Diffstat (limited to 'test/syscalls/linux/clock_gettime.cc')
-rw-r--r-- | test/syscalls/linux/clock_gettime.cc | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/test/syscalls/linux/clock_gettime.cc b/test/syscalls/linux/clock_gettime.cc new file mode 100644 index 000000000..5003928be --- /dev/null +++ b/test/syscalls/linux/clock_gettime.cc @@ -0,0 +1,156 @@ +// Copyright 2018 Google LLC +// +// 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()); + ASSERT_THAT(clock_gettime(clockid, &tp), SyscallSucceeds()); + 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"; + default: + return absl::StrCat(info.param); + } +} + +INSTANTIATE_TEST_CASE_P(ClockGettime, MonotonicClockTest, + ::testing::Values(CLOCK_MONOTONIC, + CLOCK_MONOTONIC_COARSE, + CLOCK_MONOTONIC_RAW), + PrintClockId); + +TEST(ClockGettime, UnimplementedReturnsEINVAL) { + SKIP_IF(!IsRunningOnGvisor()); + + struct timespec tp; + EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME, &tp), + SyscallFailsWithErrno(EINVAL)); + 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 |