diff --git a/doc/examples/rtde_client.rst b/doc/examples/rtde_client.rst index 5339e7198..d84bcd55d 100644 --- a/doc/examples/rtde_client.rst +++ b/doc/examples/rtde_client.rst @@ -25,6 +25,40 @@ to initialize the RTDE client. :start-at: const std::string OUTPUT_RECIPE :end-at: const std::string INPUT_RECIPE +Configuring real-time scheduling +-------------------------------- + +To reduce scheduling jitter, the example configures CPU affinity and scheduling priorities before +starting RTDE communication. + +The Universal Robots Client Library provides helper functions that simplify configuring these +settings on both Linux and Windows. + +.. literalinclude:: ../../examples/rtde_client.cpp + :language: c++ + :caption: examples/rtde_client.cpp + :linenos: + :lineno-match: + :start-at: pprocess_t process = pprocess_self(); + :end-at: URCL_LOG_ERROR("Failed to set FIFO scheduling"); + +The example assigns dedicated CPU cores and configures the highest available scheduling priority +for the current thread. + +On Linux, ``setFiFoScheduling()`` configures ``SCHED_FIFO`` scheduling. + +On Windows, ``setFiFoScheduling()`` configures the process to use +``REALTIME_PRIORITY_CLASS`` and the thread to use +``THREAD_PRIORITY_TIME_CRITICAL``. + +For a detailed explanation of the available helper functions and platform-specific real-time +configuration recommendations, see :ref:`real time setup`. + +.. note:: + + The selected CPU indices are only examples. Applications should choose CPU affinities according + to the available hardware and system configuration. + Creating an RTDE Client ----------------------- diff --git a/doc/real_time.rst b/doc/real_time.rst index 7379f1bb2..b4cc6a9f9 100644 --- a/doc/real_time.rst +++ b/doc/real_time.rst @@ -334,3 +334,168 @@ the ``cpufrequtils`` service. Check with ``cpufreq-info``. For further information about governors, please see the `kernel documentation `_. + +Windows soft real-time configuration +------------------------------------ + +Unlike Linux systems running a PREEMPT_RT kernel with ``SCHED_FIFO`` scheduling, Windows does not +provide the same level of scheduling behaviour. However, it can be configured for +soft real-time, where scheduling jitter is reduced and timing predictability is improved. + +According to Microsoft, hard real-time systems require deterministic execution at precise points in +time, whereas soft real-time systems tolerate a small amount of scheduling jitter and execution +latency. The real-time capabilities available on Windows fall into the latter category. + +For additional details, refer to Microsoft's documentation: +`Soft Real-Time `_. + +Preparing the system +^^^^^^^^^^^^^^^^^^^^ + +Microsoft provides guidance for configuring Windows 10 and Windows 11 systems for +soft real-time performance. The recommended device configuration includes techniques such as: + +* CPU isolation +* Routing interrupts away from real-time cores +* Disabling selected background services +* Disabling CPU idle states +* Configuring dedicated real-time CPU cores + +For detailed device configuration instructions, see `Device Configuration `_. + +Process and thread configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After the system has been configured, applications can improve scheduling behaviour by assigning +high priorities and CPU affinities to the processes and threads involved during execution. + +Microsoft recommends using: + +* ``SetPriorityClass()`` with ``REALTIME_PRIORITY_CLASS`` +* ``SetProcessAffinityMask()`` +* ``SetThreadPriority()`` with ``THREAD_PRIORITY_TIME_CRITICAL`` +* ``SetThreadAffinityMask()`` + +For detailed information about configuring real-time processes and threads on Windows, +see `Application Development `_. + +Scheduling priorities +^^^^^^^^^^^^^^^^^^^^^ + +Windows schedules threads using a combination of process priority class and thread priority level. + +The highest priority range is obtained by combining ``REALTIME_PRIORITY_CLASS`` and ``THREAD_PRIORITY_TIME_CRITICAL`` +which results in a base thread priority of 31, the highest priority available. + +For more information about Windows scheduling priorities, see `Scheduling Priorities `_. + +.. warning:: + + Microsoft recommends using ``REALTIME_PRIORITY_CLASS`` with care. Threads running at high + priorities for extended periods can negatively affect other system services and user interaction. + +Using the helper functions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Universal Robots Client Library provides helper functions for configuring CPU affinity and +scheduling priorities. + +Current process and thread handles can be obtained using: + +.. code-block:: cpp + + pprocess_t process = pprocess_self(); + pthread_t thread = pthread_self(); + +Linux +~~~~~ + +On Linux, the helper functions can be used to assign CPU affinity and configure +``SCHED_FIFO`` scheduling for a thread. + +.. code-block:: cpp + + pthread_t thread = pthread_self(); + + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(7, &cpuset); + + setThreadAffinity(thread, cpuset); + + int max_prio = sched_get_priority_max(SCHED_FIFO); + setFiFoScheduling(thread, max_prio); + +The configured CPU affinity applies to the selected thread. The scheduling +priority is configured using ``SCHED_FIFO``. + +Windows +~~~~~~~ + +On Windows, additional helper functions are available for configuring process +priority, process affinity, thread priority and thread affinity. + +.. code-block:: cpp + + pprocess_t process = pprocess_self(); + pthread_t thread = pthread_self(); + + DWORD_PTR process_mask = (1ULL << 6) | (1ULL << 7); + setProcessAffinity(process, process_mask); + + DWORD_PTR thread_mask = (1ULL << 7); + setThreadAffinity(thread, thread_mask); + + int max_prio = sched_get_priority_max(SCHED_FIFO); + setFiFoScheduling(thread, max_prio); + + // Equivalent explicit calls + setProcessPriority(process, REALTIME_PRIORITY_CLASS); + setThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL); + +On Windows, ``setFiFoScheduling()`` configures the process to use +``REALTIME_PRIORITY_CLASS`` and applies the requested thread priority. + +Available helper functions +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Windows: + +.. code-block:: cpp + + bool setProcessPriority(pprocess_t& process, DWORD priority); + bool setProcessAffinity(pprocess_t& process, DWORD_PTR cpu_mask); + bool setThreadPriority(pthread_t& thread, int priority); + bool setThreadAffinity(pthread_t& thread, DWORD_PTR cpu_mask); + bool setFiFoScheduling(pthread_t& thread, int priority); + +Linux: + +.. code-block:: cpp + + bool setThreadAffinity(pthread_t& thread, const cpu_set_t& cpuset); + bool setFiFoScheduling(pthread_t& thread, int priority); + +Administrative privileges +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using ``REALTIME_PRIORITY_CLASS`` may require elevated privileges depending on the system +configuration. + +When using the real-time helper functions, it is therefore recommended to run the application with +administrative privileges. + +The helper functions verify whether the process is running with elevated privileges and will issue a +warning if this is not the case. + +If sufficient privileges are not available, Windows may reject requests to enter the real-time +priority class. + +Example usage +^^^^^^^^^^^^^ + +A complete example demonstrating how to configure process priority, thread priority and CPU affinity +using the helper functions is provided in the RTDE Client example (:ref:`rtde_client_example`). + +Refer to this example for recommended usage patterns and platform-specific recommendations. + diff --git a/examples/rtde_client.cpp b/examples/rtde_client.cpp index d92605c5a..4ceb55fa6 100644 --- a/examples/rtde_client.cpp +++ b/examples/rtde_client.cpp @@ -59,6 +59,43 @@ void printFraction(const double fraction, const std::string& label, const size_t int main(int argc, char* argv[]) { + pthread_t thread = pthread_self(); +#ifdef _WIN32 + pprocess_t process = pprocess_self(); + + // Assign logical CPUs 6 and 7 to this process + DWORD_PTR process_mask = (1ULL << 6) | (1ULL << 7); + if (!setProcessAffinity(process, process_mask)) + { + URCL_LOG_ERROR("Failed to set process affinity"); + } + + // Assign logical CPU 7 to this thread + DWORD_PTR thread_mask = (1ULL << 7); + if (!setThreadAffinity(thread, thread_mask)) + { + URCL_LOG_ERROR("Failed to set thread affinity"); + } +#elif __linux__ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + + // Assign logical CPU 7 to this thread + CPU_SET(7, &cpuset); + + if (!setThreadAffinity(thread, cpuset)) + { + URCL_LOG_ERROR("Failed to set thread affinity"); + } +#endif + + // Set thread and process to maximum priority + const int max_prio = sched_get_priority_max(SCHED_FIFO); + if (!setFiFoScheduling(thread, max_prio)) + { + URCL_LOG_ERROR("Failed to set FIFO scheduling"); + } + // Parse the ip arguments if given std::string robot_ip = DEFAULT_ROBOT_IP; if (argc > 1) @@ -143,4 +180,4 @@ int main(int argc, char* argv[]) URCL_LOG_INFO("Exiting RTDE read/write example."); return 0; -} +} \ No newline at end of file diff --git a/include/ur_client_library/helpers.h b/include/ur_client_library/helpers.h index f0af4e248..26015c382 100644 --- a/include/ur_client_library/helpers.h +++ b/include/ur_client_library/helpers.h @@ -48,12 +48,18 @@ # define SCHED_FIFO (1) typedef HANDLE pthread_t; +typedef HANDLE pprocess_t; static inline pthread_t pthread_self() { return ::GetCurrentThread(); } +static inline pprocess_t pprocess_self() +{ + return ::GetCurrentProcess(); +} + static inline int sched_get_priority_max(int policy) { (void)policy; @@ -96,7 +102,88 @@ static inline int sched_get_priority_max(int policy) namespace urcl { -bool setFiFoScheduling(pthread_t& thread, const int priority); + +#ifdef _WIN32 + +/*! + * \brief Set the priority class of a process. + * + * Wraps the corresponding operating system API and verifies that the requested + * priority class has been applied successfully. + * + * \param process Process handle. + * \param priority Windows process priority class. + * + * \returns True if the priority class was applied successfully. + */ +bool setProcessPriority(pprocess_t& process, DWORD priority); + +/*! + * \brief Restrict a process to a set of CPUs. + * + * Wraps the corresponding operating system API and verifies that the requested + * CPU affinity mask has been applied successfully. + * + * \param process Process handle. + * \param cpu_mask Bit mask describing the CPUs available to the process. + * + * \returns True if the affinity mask was applied successfully. + */ +bool setProcessAffinity(pprocess_t& process, DWORD_PTR cpu_mask); + +/*! + * \brief Restrict a thread to a set of CPUs. + * + * \param thread Thread handle. + * \param cpu_mask Bit mask describing the CPUs available to the thread. + * + * \returns True if the affinity mask was applied successfully. + */ +bool setThreadAffinity(pthread_t& thread, DWORD_PTR cpu_mask); + +/*! + * \brief Set the scheduling priority of a thread. + * + * Wraps the corresponding operating system API and verifies that the requested + * priority has been applied successfully. + * + * \param thread Thread handle. + * \param priority Windows thread priority value. + * + * \returns True if the priority was applied successfully. + */ +bool setThreadPriority(pthread_t& thread, const int priority); + +#elif __linux__ + +/*! + * \brief Restrict a thread to a set of CPUs. + * + * \param thread Thread handle. + * \param cpuset Set of CPUs available to the thread. + * + * \returns True if the CPU affinity was applied successfully. + */ +bool setThreadAffinity(pthread_t& thread, const cpu_set_t& cpuset); + +#endif + +/*! + * \brief Configure high-priority scheduling for a thread. + * + * On Linux, this configures ``SCHED_FIFO`` scheduling using the supplied + * priority. + * + * On Windows, this configures the process to use + * ``REALTIME_PRIORITY_CLASS`` and sets the thread to the supplied Windows + * thread priority. + * + * \param thread Thread handle. + * \param priority Scheduling priority to apply. + * + * \returns True if the scheduling configuration was applied successfully. + */ +bool setFiFoScheduling(pthread_t& thread, int priority); /*! * \brief Wait for a condition to be true. diff --git a/src/helpers.cpp b/src/helpers.cpp index 4535c6d3b..d4cdce0bc 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -38,6 +38,7 @@ #include #include #include +#include // clang-format off // We want to keep the URL in one line to avoid formatting issues. This will make it easier to @@ -47,10 +48,383 @@ const std::string RT_DOC_URL = "https://docs.universal-robots.com/Universal_Robo namespace urcl { -bool setFiFoScheduling(pthread_t& thread, const int priority) + +#ifdef _WIN32 + +const char* processPriorityToString(DWORD priority) +{ + switch (priority) + { + case IDLE_PRIORITY_CLASS: + return "IDLE"; + case BELOW_NORMAL_PRIORITY_CLASS: + return "BELOW_NORMAL"; + case NORMAL_PRIORITY_CLASS: + return "NORMAL"; + case ABOVE_NORMAL_PRIORITY_CLASS: + return "ABOVE_NORMAL"; + case HIGH_PRIORITY_CLASS: + return "HIGH"; + case REALTIME_PRIORITY_CLASS: + return "REALTIME"; + default: + return "UNKNOWN"; + } +} + +const char* threadPriorityToString(int priority) +{ + switch (priority) + { + case THREAD_PRIORITY_LOWEST: + return "LOWEST"; + case THREAD_PRIORITY_BELOW_NORMAL: + return "BELOW_NORMAL"; + case THREAD_PRIORITY_NORMAL: + return "NORMAL"; + case THREAD_PRIORITY_ABOVE_NORMAL: + return "ABOVE_NORMAL"; + case THREAD_PRIORITY_HIGHEST: + return "HIGHEST"; + case THREAD_PRIORITY_TIME_CRITICAL: + return "TIME_CRITICAL"; + default: + return "UNKNOWN"; + } +} + +bool isProcessElevated() +{ + bool isElevated = false; + HANDLE hToken = nullptr; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + TOKEN_ELEVATION elevation; + DWORD dwSize = sizeof(elevation); + if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) + { + isElevated = elevation.TokenIsElevated; + } + } + if (hToken) + { + CloseHandle(hToken); + } + return isElevated; +} + +std::string maskToCpuList(uint64_t mask) +{ + std::string out = "["; + for (int i = 0; i < 64; ++i) + { + if (mask & (1ULL << i)) + { + if (out.size() > 1) + out += ","; + out += std::to_string(i); + } + } + out += "]"; + return out; +} + +std::string getLastWindowsErrorMsg(DWORD error_id) +{ + LPSTR buffer = nullptr; + + DWORD size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr); + + if (size == 0 || buffer == nullptr) + { + return "Unknown Windows error"; + } + + std::string message(buffer, size); + LocalFree(buffer); + + return message; +}; + +bool setProcessPriority(pprocess_t& process, DWORD priority) +{ + if (!isProcessElevated() && priority == REALTIME_PRIORITY_CLASS) + { + URCL_LOG_WARN("Process is not running with elevated privileges (UAC). " + "REALTIME_PRIORITY_CLASS may fail. Try 'Run as Administrator'."); + } + + DWORD old_process_priority = ::GetPriorityClass(process); + if (old_process_priority == 0) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in retrieving the current process priority. Error: %lu (%s)", err, + getLastWindowsErrorMsg(err).c_str()); + return false; + } + + auto restore_priority = [&]() { + if (!::SetPriorityClass(process, old_process_priority)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Failed to restore previous process priority %s (0x%X). Error: %lu (%s)", + processPriorityToString(old_process_priority), old_process_priority, err, + getLastWindowsErrorMsg(err).c_str()); + } + else + { + URCL_LOG_INFO("Previous process priority successfully restored to %s (0x%X)", + processPriorityToString(old_process_priority), old_process_priority); + } + }; + + if (!::SetPriorityClass(process, priority)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in setting the process priority to %s (%llX). Error: %lu (%s)", + processPriorityToString(priority), priority, err, getLastWindowsErrorMsg(err).c_str()); + return false; + } + + DWORD priority_applied = ::GetPriorityClass(process); + if (priority_applied == 0) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in retrieving the process priority for verification. Error: %lu (%s)", err, + getLastWindowsErrorMsg(err).c_str()); + restore_priority(); + return false; + } + + URCL_LOG_INFO("Process priority successfully set to %s (0x%X)", processPriorityToString(priority_applied), + priority_applied); + + if (priority_applied != priority) + { + URCL_LOG_WARN("Process priority mismatch. Expected %s (0x%X), got %s (0x%X)", processPriorityToString(priority), + priority, processPriorityToString(priority_applied), priority_applied); + restore_priority(); + return false; + } + + return true; +} + +bool setProcessAffinity(pprocess_t& process, DWORD_PTR cpu_mask) +{ + if (!::SetProcessAffinityMask(process, cpu_mask)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in setting process affinity to CPUs %s (mask=0x%llX). Error: %lu (%s)", + maskToCpuList(cpu_mask).c_str(), static_cast(cpu_mask), err, + getLastWindowsErrorMsg(err).c_str()); + + return false; + } + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (!::GetProcessAffinityMask(process, &process_mask, &system_mask)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in setting process affinity to %s. Error: %lu (%s)", maskToCpuList(cpu_mask).c_str(), + err, getLastWindowsErrorMsg(err).c_str()); + return false; + } + URCL_LOG_INFO("Process affinity set to CPUs %s (mask=0x%llX)", maskToCpuList(process_mask).c_str(), + static_cast(process_mask)); + if (process_mask != cpu_mask) + { + URCL_LOG_WARN("Process affinity mismatch. Expected %s, got %s", maskToCpuList(cpu_mask).c_str(), + maskToCpuList(process_mask).c_str()); + return false; + } + return true; +} + +bool setThreadAffinity(pthread_t& thread, DWORD_PTR cpu_mask) +{ + DWORD_PTR result = ::SetThreadAffinityMask(thread, cpu_mask); + + if (result == 0) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in setting thread affinity to %s. Error: %lu (%s)", maskToCpuList(cpu_mask).c_str(), + err, getLastWindowsErrorMsg(err).c_str()); + return false; + } + + URCL_LOG_INFO("Thread affinity successfully set to CPUs %s (mask=0x%llX)", maskToCpuList(cpu_mask).c_str(), + static_cast(cpu_mask)); + return true; +} + +bool setThreadPriority(pthread_t& thread, const int priority) +{ + int old_priority = ::GetThreadPriority(thread); + if (old_priority == THREAD_PRIORITY_ERROR_RETURN) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in retrieving the current thread priority. Error: %lu (%s)", err, + getLastWindowsErrorMsg(err).c_str()); + return false; + } + + auto restore_priority = [&]() { + if (!::SetThreadPriority(thread, old_priority)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Failed to restore previous thread priority %s (%d). Error: %lu (%s)", + threadPriorityToString(old_priority), old_priority, err, getLastWindowsErrorMsg(err).c_str()); + } + else + { + URCL_LOG_INFO("Previous thread priority successfully restored to %s (%d)", threadPriorityToString(old_priority), + old_priority); + } + }; + + if (!::SetThreadPriority(thread, priority)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in setting thread priority to %s (%d). Error: %lu (%s)", + threadPriorityToString(priority), priority, err, getLastWindowsErrorMsg(err).c_str()); + + return false; + } + + int applied = ::GetThreadPriority(thread); + if (applied == THREAD_PRIORITY_ERROR_RETURN) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in retrieving the thread priority for verification. Error: %lu (%s)", err, + getLastWindowsErrorMsg(err).c_str()); + restore_priority(); + return false; + } + + URCL_LOG_INFO("Thread priority successfully set to %s (%d)", threadPriorityToString(applied), applied); + + if (applied != priority) + { + URCL_LOG_WARN("Thread priority mismatch. Expected %s (%d), got %s (%d)", threadPriorityToString(priority), priority, + threadPriorityToString(applied), applied); + restore_priority(); + return false; + } + + return true; +} + +#elif __linux__ + +std::string cpuSetToString(const cpu_set_t& cpuset) +{ + std::string out = "["; + + for (int i = 0; i < CPU_SETSIZE; ++i) + { + if (CPU_ISSET(i, &cpuset)) + { + if (out.size() > 1) + { + out += ","; + } + out += std::to_string(i); + } + } + + out += "]"; + return out; +} + +bool setThreadAffinity(pthread_t& thread, const cpu_set_t& cpuset) +{ + int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); + + if (ret != 0) + { + URCL_LOG_ERROR("Unsuccessful in setting thread affinity. Error: %s", strerror(ret)); + return false; + } + + cpu_set_t applied_set; + CPU_ZERO(&applied_set); + + if (pthread_getaffinity_np(thread, sizeof(cpu_set_t), &applied_set) == 0) + { + bool match = true; + + for (int i = 0; i < CPU_SETSIZE; ++i) + { + if (CPU_ISSET(i, &cpuset) != CPU_ISSET(i, &applied_set)) + { + match = false; + break; + } + } + + if (!match) + { + URCL_LOG_WARN("Thread affinity mismatch. Requested %s, got %s", cpuSetToString(cpuset).c_str(), + cpuSetToString(applied_set).c_str()); + + return false; + } + } + else + { + URCL_LOG_WARN("Could not retrieve thread affinity"); + return false; + } + + URCL_LOG_INFO("Thread affinity successfully set to CPUs %s", cpuSetToString(applied_set).c_str()); + + return true; +} + +#endif + +bool setFiFoScheduling(pthread_t& thread, int priority) { #ifdef _WIN32 - return ::SetThreadPriority(thread, priority); + pprocess_t process = pprocess_self(); + + DWORD old_process_priority = ::GetPriorityClass(process); + if (old_process_priority == 0) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Unsuccessful in retrieving the current process priority. Error: %lu (%s)", err, + getLastWindowsErrorMsg(err).c_str()); + return false; + } + + if (!setProcessPriority(process, REALTIME_PRIORITY_CLASS)) + { + URCL_LOG_ERROR("Unsuccessful in setting process to REALTIME_PRIORITY_CLASS"); + return false; + } + + if (!setThreadPriority(thread, priority)) + { + URCL_LOG_ERROR("Unsuccessful in setting thread priority to %s (%d)", threadPriorityToString(priority), priority); + URCL_LOG_INFO("Restoring previous process priority %s (0x%X)", processPriorityToString(old_process_priority), + old_process_priority); + + if (!::SetPriorityClass(process, old_process_priority)) + { + DWORD err = GetLastError(); + URCL_LOG_ERROR("Failed to restore previous process priority %s (0x%X). Error: %lu (%s)", + processPriorityToString(old_process_priority), old_process_priority, err, + getLastWindowsErrorMsg(err).c_str()); + } + + return false; + } + + return true; + #else // _WIN32 struct sched_param params; params.sched_priority = priority; diff --git a/tests/test_helpers.cpp b/tests/test_helpers.cpp index 3499b53c2..0220a5123 100644 --- a/tests/test_helpers.cpp +++ b/tests/test_helpers.cpp @@ -145,3 +145,166 @@ TEST(TestHelpers, robotSeriesString) EXPECT_EQ(robotSeriesString(RobotSeries::UR_SERIES), "UR_SERIES"); EXPECT_EQ(robotSeriesString(RobotSeries::UNDEFINED), "UNDEFINED"); } + +// Thread Affinity Tests + +TEST(TestHelpers, setThreadAffinity_basic) +{ +#ifdef _WIN32 + pthread_t thread = pthread_self(); + DWORD_PTR mask = (1ULL << 0); + EXPECT_TRUE(setThreadAffinity(thread, mask)); +#elif __linux__ + pthread_t thread = pthread_self(); + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + EXPECT_TRUE(setThreadAffinity(thread, cpuset)); +#endif +} + +TEST(TestHelpers, setThreadAffinity_mult) +{ +#ifdef _WIN32 + pthread_t thread = pthread_self(); + DWORD_PTR mask = (1ULL << 0) | (1ULL << 1); + EXPECT_TRUE(setThreadAffinity(thread, mask)); +#elif __linux__ + pthread_t thread = pthread_self(); + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + CPU_SET(1, &cpuset); + EXPECT_TRUE(setThreadAffinity(thread, cpuset)); +#endif +} + +TEST(TestHelpers, setThreadAffinity_invalid) +{ +#ifdef _WIN32 + pthread_t thread = pthread_self(); + uint64_t invalid_mask = 1ULL << 63; + EXPECT_FALSE(setThreadAffinity(thread, invalid_mask)); +#elif __linux__ + pthread_t thread = pthread_self(); + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(63, &cpuset); + EXPECT_FALSE(setThreadAffinity(thread, cpuset)); +#endif +} + +TEST(TestHelpers, setThreadAffinity_invalidHandle) +{ +#ifdef _WIN32 + pthread_t thread = nullptr; + EXPECT_FALSE(setThreadAffinity(thread, (1ULL << 0))); +#elif __linux__ + pthread_t thread{}; + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + EXPECT_DEATH(setThreadAffinity(thread, cpuset), ""); +#endif +} + +TEST(TestHelpers, setThreadAffinity_empty) +{ +#ifdef _WIN32 + pthread_t thread = pthread_self(); + EXPECT_FALSE(setThreadAffinity(thread, 0)); +#elif __linux__ + pthread_t thread = pthread_self(); + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + EXPECT_FALSE(setThreadAffinity(thread, cpuset)); +#endif +} + +// Thread Priority Tests + +#ifdef _WIN32 +TEST(TestHelpers, setThreadPriority_allValidPriorities) +{ + pthread_t thread = pthread_self(); + + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_LOWEST)); + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_BELOW_NORMAL)); + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_NORMAL)); + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_ABOVE_NORMAL)); + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_HIGHEST)); + EXPECT_TRUE(setThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL)); +} + +TEST(TestHelpers, setThreadPriority_invalid) +{ + pthread_t thread = pthread_self(); + EXPECT_FALSE(setThreadPriority(thread, 999999)); +} + +TEST(TestHelpers, setThreadPriority_invalidHandle) +{ + pthread_t thread = nullptr; + EXPECT_FALSE(setThreadPriority(thread, THREAD_PRIORITY_HIGHEST)); +} + +// Process Priority tests + +TEST(TestHelpers, setProcessPriority_allValidPriorities) +{ + pprocess_t process = pprocess_self(); + + EXPECT_TRUE(setProcessPriority(process, IDLE_PRIORITY_CLASS)); + EXPECT_TRUE(setProcessPriority(process, BELOW_NORMAL_PRIORITY_CLASS)); + EXPECT_TRUE(setProcessPriority(process, NORMAL_PRIORITY_CLASS)); + EXPECT_TRUE(setProcessPriority(process, ABOVE_NORMAL_PRIORITY_CLASS)); + EXPECT_TRUE(setProcessPriority(process, HIGH_PRIORITY_CLASS)); +} + +TEST(TestHelpers, setProcessPriority_invalid) +{ + pprocess_t process = pprocess_self(); + EXPECT_FALSE(setProcessPriority(process, 999999)); +} + +TEST(TestHelpers, setProcessPriority_invalidHandle) +{ + pprocess_t process = nullptr; + EXPECT_FALSE(setProcessPriority(process, NORMAL_PRIORITY_CLASS)); +} + +// Process Affinity Tests + +TEST(TestHelpers, setProcessAffinity_basic) +{ + pprocess_t process = pprocess_self(); + DWORD_PTR mask = (1ULL << 0); + EXPECT_TRUE(setProcessAffinity(process, mask)); +} + +TEST(TestHelpers, setProcessAffinity_multi) +{ + pprocess_t process = pprocess_self(); + DWORD_PTR mask = (1ULL << 0) | (1ULL << 1); + EXPECT_TRUE(setProcessAffinity(process, mask)); +} + +TEST(TestHelpers, setProcessAffinity_invalidHandle) +{ + pprocess_t process = nullptr; + EXPECT_FALSE(setProcessAffinity(process, (1ULL << 0))); +} + +TEST(TestHelpers, setProcessAffinity_zeroMask) +{ + pprocess_t process = pprocess_self(); + EXPECT_FALSE(setProcessAffinity(process, 0)); +} + +TEST(TestHelpers, setProcessAffinity_invalidMask) +{ + pprocess_t process = pprocess_self(); + DWORD_PTR mask = 0xFFFFFFFFFFFFFFFFULL; + EXPECT_FALSE(setProcessAffinity(process, mask)); +} +#endif