diff --git a/src/Native/Unix/System.Native/pal_time.c b/src/Native/Unix/System.Native/pal_time.c index 94cc61a3f2d8..33f168f3de26 100644 --- a/src/Native/Unix/System.Native/pal_time.c +++ b/src/Native/Unix/System.Native/pal_time.c @@ -12,6 +12,7 @@ #include #include #include +#include #if HAVE_MACH_ABSOLUTE_TIME #include #endif @@ -152,3 +153,82 @@ int32_t SystemNative_GetTimebaseInfo(uint32_t* numer, uint32_t* denom) } return 1; } + +#if defined(_ARM_) || defined(_ARM64_) +#define SYSCONF_GET_NUMPROCS _SC_NPROCESSORS_CONF +#else +#define SYSCONF_GET_NUMPROCS _SC_NPROCESSORS_ONLN +#endif + +int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo) +{ + static long numProcessors = 0; + + if (numProcessors <= 0) + { + numProcessors = sysconf(SYSCONF_GET_NUMPROCS); + if (numProcessors <= 0) + { + return 0; + } + } + + uint64_t kernelTime = 0; + uint64_t userTime = 0; + + struct rusage resUsage; + if (getrusage(RUSAGE_SELF, &resUsage) == -1) + { + assert(false); + return 0; + } + else + { + kernelTime = ((uint64_t)(resUsage.ru_stime.tv_sec) * SecondsToNanoSeconds) + (uint64_t)(resUsage.ru_stime.tv_usec); + userTime = ((uint64_t)(resUsage.ru_utime.tv_sec) * SecondsToNanoSeconds) + (uint64_t)(resUsage.ru_utime.tv_usec); + } + + uint64_t timestamp; + uint64_t resolution; + + if (!SystemNative_GetTimestamp(×tamp) || !SystemNative_GetTimestampResolution(&resolution)) + { + return 0; + } + + uint64_t currentTime = timestamp * SecondsToNanoSeconds / resolution; + + uint64_t lastRecordedCurrentTime = previousCpuInfo->lastRecordedCurrentTime; + uint64_t lastRecordedKernelTime = previousCpuInfo->lastRecordedKernelTime; + uint64_t lastRecordedUserTime = previousCpuInfo->lastRecordedUserTime; + + uint64_t cpuTotalTime = 0; + if (currentTime > lastRecordedCurrentTime) + { + // cpuTotalTime is based on clock time. Since multiple threads can run in parallel, + // we need to scale cpuTotalTime cover the same amount of total CPU time. + // rusage time is already scaled across multiple processors. + cpuTotalTime = (currentTime - lastRecordedCurrentTime); + cpuTotalTime *= (uint64_t)numProcessors; + } + + uint64_t cpuBusyTime = 0; + if (userTime >= lastRecordedUserTime && kernelTime >= lastRecordedKernelTime) + { + cpuBusyTime = (userTime - lastRecordedUserTime) + (kernelTime - lastRecordedKernelTime); + } + + int32_t cpuUtilization = 0; + if (cpuTotalTime > 0 && cpuBusyTime > 0) + { + cpuUtilization = (int32_t)(cpuBusyTime / cpuTotalTime); + } + + assert(cpuUtilization >= 0 && cpuUtilization <= 100); + + previousCpuInfo->lastRecordedCurrentTime = currentTime; + previousCpuInfo->lastRecordedUserTime = userTime; + previousCpuInfo->lastRecordedKernelTime = kernelTime; + + return cpuUtilization; +} diff --git a/src/Native/Unix/System.Native/pal_time.h b/src/Native/Unix/System.Native/pal_time.h index a0deba8ff266..ebeee545cdf1 100644 --- a/src/Native/Unix/System.Native/pal_time.h +++ b/src/Native/Unix/System.Native/pal_time.h @@ -13,6 +13,14 @@ typedef struct TimeSpec int64_t tv_nsec; // nanoseconds } TimeSpec; +typedef struct ProcessCpuInformation +{ + uint64_t lastRecordedCurrentTime; + uint64_t lastRecordedKernelTime; + uint64_t lastRecordedUserTime; +} ProcessCpuInformation; + + /** * Sets the last access and last modified time of a file * @@ -37,3 +45,12 @@ DLLEXPORT int32_t SystemNative_GetTimestamp(uint64_t* timestamp); DLLEXPORT int32_t SystemNative_GetAbsoluteTime(uint64_t* timestamp); DLLEXPORT int32_t SystemNative_GetTimebaseInfo(uint32_t* numer, uint32_t* denom); + +/** + * The main purpose of this function is to compute the overall CPU utilization + * for the CLR thread pool to regulate the number of worker threads. + * Since there is no consistent API on Unix to get the CPU utilization + * from a user process, getrusage and gettimeofday are used to + * compute the current process's CPU utilization instead. + */ +DLLEXPORT int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo);