diff options
Diffstat (limited to 'test/syscalls/linux/itimer.cc')
-rw-r--r-- | test/syscalls/linux/itimer.cc | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/test/syscalls/linux/itimer.cc b/test/syscalls/linux/itimer.cc new file mode 100644 index 000000000..ee5871cbe --- /dev/null +++ b/test/syscalls/linux/itimer.cc @@ -0,0 +1,342 @@ +// 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 <signal.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> + +#include <atomic> +#include <functional> +#include <iostream> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/file_descriptor.h" +#include "test/util/logging.h" +#include "test/util/multiprocess_util.h" +#include "test/util/posix_error.h" +#include "test/util/signal_util.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" +#include "test/util/timer_util.h" + +namespace gvisor { +namespace testing { +namespace { + +constexpr char kSIGALRMToMainThread[] = "--itimer_sigarlm_to_main_thread"; +constexpr char kSIGPROFFairnessActive[] = "--itimer_sigprof_fairness_active"; +constexpr char kSIGPROFFairnessIdle[] = "--itimer_sigprof_fairness_idle"; + +// Time period to be set for the itimers. +constexpr absl::Duration kPeriod = absl::Milliseconds(25); +// Total amount of time to spend per thread. +constexpr absl::Duration kTestDuration = absl::Seconds(20); +// Amount of spin iterations to perform as the minimum work item per thread. +// Chosen to be sub-millisecond range. +constexpr int kIterations = 10000000; +// Allow deviation in the number of samples. +constexpr double kNumSamplesDeviationRatio = 0.2; +constexpr double kNumSamplesMinRatio = 0.5; + +TEST(ItimerTest, ItimervalUpdatedBeforeExpiration) { + constexpr int kSleepSecs = 10; + constexpr int kAlarmSecs = 15; + static_assert( + kSleepSecs < kAlarmSecs, + "kSleepSecs must be less than kAlarmSecs for the test to be meaningful"); + constexpr int kMaxRemainingSecs = kAlarmSecs - kSleepSecs; + + // Install a no-op handler for SIGALRM. + struct sigaction sa = {}; + sigfillset(&sa.sa_mask); + sa.sa_handler = +[](int signo) {}; + auto const cleanup_sa = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); + + // Set an itimer-based alarm for kAlarmSecs from now. + struct itimerval itv = {}; + itv.it_value.tv_sec = kAlarmSecs; + auto const cleanup_itimer = + ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv)); + + // After sleeping for kSleepSecs, the itimer value should reflect the elapsed + // time even if it hasn't expired. + absl::SleepFor(absl::Seconds(kSleepSecs)); + ASSERT_THAT(getitimer(ITIMER_REAL, &itv), SyscallSucceeds()); + EXPECT_TRUE( + itv.it_value.tv_sec < kMaxRemainingSecs || + (itv.it_value.tv_sec == kMaxRemainingSecs && itv.it_value.tv_usec == 0)) + << "Remaining time: " << itv.it_value.tv_sec << " seconds + " + << itv.it_value.tv_usec << " microseconds"; +} + +ABSL_CONST_INIT static thread_local std::atomic_int signal_test_num_samples = + ATOMIC_VAR_INIT(0); + +void SignalTestSignalHandler(int /*signum*/) { signal_test_num_samples++; } + +struct SignalTestResult { + int expected_total; + int main_thread_samples; + std::vector<int> worker_samples; +}; + +std::ostream& operator<<(std::ostream& os, const SignalTestResult& r) { + os << "{expected_total: " << r.expected_total + << ", main_thread_samples: " << r.main_thread_samples + << ", worker_samples: ["; + bool first = true; + for (int sample : r.worker_samples) { + if (!first) { + os << ", "; + } + os << sample; + first = false; + } + os << "]}"; + return os; +} + +// Starts two worker threads and itimer id and measures the number of signal +// delivered to each thread. +SignalTestResult ItimerSignalTest(int id, clock_t main_clock, + clock_t worker_clock, int signal, + absl::Duration sleep) { + signal_test_num_samples = 0; + + struct sigaction sa = {}; + sa.sa_handler = &SignalTestSignalHandler; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + auto sigaction_cleanup = std::move(ScopedSigaction(signal, sa).ValueOrDie()); + + int socketfds[2]; + TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, socketfds) == 0); + + // Do the spinning in the workers. + std::function<void*(int)> work = [&](int socket_fd) { + FileDescriptor fd(socket_fd); + + absl::Time finish = Now(worker_clock) + kTestDuration; + while (Now(worker_clock) < finish) { + // Blocked on read. + char c; + RetryEINTR(read)(fd.get(), &c, 1); + for (int i = 0; i < kIterations; i++) { + // Ensure compiler won't optimize this loop away. + asm(""); + } + + if (sleep != absl::ZeroDuration()) { + // Sleep so that the entire process is idle for a while. + absl::SleepFor(sleep); + } + + // Unblock the other thread. + RetryEINTR(write)(fd.get(), &c, 1); + } + + return reinterpret_cast<void*>(signal_test_num_samples.load()); + }; + + ScopedThread th1( + static_cast<std::function<void*()>>(std::bind(work, socketfds[0]))); + ScopedThread th2( + static_cast<std::function<void*()>>(std::bind(work, socketfds[1]))); + + absl::Time start = Now(main_clock); + // Start the timer. + struct itimerval timer = {}; + timer.it_value = absl::ToTimeval(kPeriod); + timer.it_interval = absl::ToTimeval(kPeriod); + auto cleanup_itimer = std::move(ScopedItimer(id, timer).ValueOrDie()); + + // Unblock th1. + // + // N.B. th2 owns socketfds[1] but can't close it until it unblocks. + char c = 0; + TEST_CHECK(write(socketfds[1], &c, 1) == 1); + + SignalTestResult result; + + // Wait for the workers to be done and collect their sample counts. + result.worker_samples.push_back(reinterpret_cast<int64_t>(th1.Join())); + result.worker_samples.push_back(reinterpret_cast<int64_t>(th2.Join())); + cleanup_itimer.Release()(); + result.expected_total = (Now(main_clock) - start) / kPeriod; + result.main_thread_samples = signal_test_num_samples.load(); + + return result; +} + +int TestSIGALRMToMainThread() { + SignalTestResult result = + ItimerSignalTest(ITIMER_REAL, CLOCK_REALTIME, CLOCK_REALTIME, SIGALRM, + absl::ZeroDuration()); + + std::cerr << "result: " << result << std::endl; + + // ITIMER_REAL-generated SIGALRMs prefer to deliver to the thread group leader + // (but don't guarantee it), so we expect to see most samples on the main + // thread. + // + // Linux only guarantees timers will never expire before the requested time. + // Thus, we only check the upper bound and also it at least have one sample. + TEST_CHECK(result.main_thread_samples <= result.expected_total); + TEST_CHECK(result.main_thread_samples > 0); + for (int num : result.worker_samples) { + TEST_CHECK_MSG(num <= 50, "worker received too many samples"); + } + + return 0; +} + +// Random save/restore is disabled as it introduces additional latency and +// unpredictable distribution patterns. +TEST(ItimerTest, DeliversSIGALRMToMainThread_NoRandomSave) { + pid_t child; + int execve_errno; + auto kill = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGALRMToMainThread}, + {}, &child, &execve_errno)); + EXPECT_EQ(0, execve_errno); + + int status; + EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), + SyscallSucceedsWithValue(child)); + + // Not required anymore. + kill.Release(); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; +} + +// Signals are delivered to threads fairly. +// +// sleep indicates how long to sleep worker threads each iteration to make the +// entire process idle. +int TestSIGPROFFairness(absl::Duration sleep) { + SignalTestResult result = + ItimerSignalTest(ITIMER_PROF, CLOCK_PROCESS_CPUTIME_ID, + CLOCK_THREAD_CPUTIME_ID, SIGPROF, sleep); + + std::cerr << "result: " << result << std::endl; + + // The number of samples on the main thread should be very low as it did + // nothing. + TEST_CHECK(result.main_thread_samples < 60); + + // Both workers should get roughly equal number of samples. + TEST_CHECK(result.worker_samples.size() == 2); + + TEST_CHECK(result.expected_total > 0); + + // In an ideal world each thread would get exactly 50% of the signals, + // but since that's unlikely to happen we allow for them to get no less than + // kNumSamplesDeviationRatio of the total observed samples. + TEST_CHECK_MSG(std::abs(result.worker_samples[0] - result.worker_samples[1]) < + ((result.worker_samples[0] + result.worker_samples[1]) * + kNumSamplesDeviationRatio), + "one worker received disproportionate share of samples"); + + return 0; +} + +// Random save/restore is disabled as it introduces additional latency and +// unpredictable distribution patterns. +TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyActive_NoRandomSave) { + pid_t child; + int execve_errno; + auto kill = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessActive}, + {}, &child, &execve_errno)); + EXPECT_EQ(0, execve_errno); + + int status; + EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), + SyscallSucceedsWithValue(child)); + + // Not required anymore. + kill.Release(); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << "Exited with code: " << status; +} + +// Random save/restore is disabled as it introduces additional latency and +// unpredictable distribution patterns. +TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyIdle_NoRandomSave) { + pid_t child; + int execve_errno; + auto kill = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessIdle}, + {}, &child, &execve_errno)); + EXPECT_EQ(0, execve_errno); + + int status; + EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), + SyscallSucceedsWithValue(child)); + + // Not required anymore. + kill.Release(); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << "Exited with code: " << status; +} + +} // namespace +} // namespace testing +} // namespace gvisor + +namespace { +void MaskSIGPIPE() { + // Always mask SIGPIPE as it's common and tests aren't expected to handle it. + // We don't take the TestInit() path so we must do this manually. + struct sigaction sa = {}; + sa.sa_handler = SIG_IGN; + TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0); +} +} // namespace + +int main(int argc, char** argv) { + // These tests require no background threads, so check for them before + // TestInit. + for (int i = 0; i < argc; i++) { + absl::string_view arg(argv[i]); + + if (arg == gvisor::testing::kSIGALRMToMainThread) { + MaskSIGPIPE(); + return gvisor::testing::TestSIGALRMToMainThread(); + } + if (arg == gvisor::testing::kSIGPROFFairnessActive) { + MaskSIGPIPE(); + return gvisor::testing::TestSIGPROFFairness(absl::ZeroDuration()); + } + if (arg == gvisor::testing::kSIGPROFFairnessIdle) { + MaskSIGPIPE(); + return gvisor::testing::TestSIGPROFFairness(absl::Milliseconds(10)); + } + } + + gvisor::testing::TestInit(&argc, &argv); + + return RUN_ALL_TESTS(); +} |