| // Copyright 2017 The Abseil 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 "absl/base/internal/sysinfo.h" |
| |
| #include "absl/base/attributes.h" |
| |
| #ifdef _WIN32 |
| #include <shlwapi.h> |
| #include <windows.h> |
| #else |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #endif |
| |
| #ifdef __linux__ |
| #include <sys/syscall.h> |
| #endif |
| |
| #if defined(__APPLE__) || defined(__FreeBSD__) |
| #include <sys/sysctl.h> |
| #endif |
| |
| #if defined(__myriad2__) |
| #include <rtems.h> |
| #endif |
| |
| #include <string.h> |
| #include <cassert> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <ctime> |
| #include <limits> |
| #include <thread> // NOLINT(build/c++11) |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/call_once.h" |
| #include "absl/base/internal/raw_logging.h" |
| #include "absl/base/internal/spinlock.h" |
| #include "absl/base/internal/unscaledcycleclock.h" |
| |
| namespace absl { |
| namespace base_internal { |
| |
| static once_flag init_system_info_once; |
| static int num_cpus = 0; |
| static double nominal_cpu_frequency = 1.0; // 0.0 might be dangerous. |
| |
| static int GetNumCPUs() { |
| #if defined(__myriad2__) |
| return 1; |
| #else |
| // Other possibilities: |
| // - Read /sys/devices/system/cpu/online and use cpumask_parse() |
| // - sysconf(_SC_NPROCESSORS_ONLN) |
| return std::thread::hardware_concurrency(); |
| #endif |
| } |
| |
| #if defined(_WIN32) |
| |
| static double GetNominalCPUFrequency() { |
| DWORD data; |
| DWORD data_size = sizeof(data); |
| #pragma comment(lib, "shlwapi.lib") // For SHGetValue(). |
| if (SUCCEEDED( |
| SHGetValueA(HKEY_LOCAL_MACHINE, |
| "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", |
| "~MHz", nullptr, &data, &data_size))) { |
| return data * 1e6; // Value is MHz. |
| } |
| return 1.0; |
| } |
| |
| #elif defined(CTL_HW) && defined(HW_CPU_FREQ) |
| |
| static double GetNominalCPUFrequency() { |
| unsigned freq; |
| size_t size = sizeof(freq); |
| int mib[2] = {CTL_HW, HW_CPU_FREQ}; |
| if (sysctl(mib, 2, &freq, &size, nullptr, 0) == 0) { |
| return static_cast<double>(freq); |
| } |
| return 1.0; |
| } |
| |
| #else |
| |
| // Helper function for reading a long from a file. Returns true if successful |
| // and the memory location pointed to by value is set to the value read. |
| static bool ReadLongFromFile(const char *file, long *value) { |
| bool ret = false; |
| int fd = open(file, O_RDONLY); |
| if (fd != -1) { |
| char line[1024]; |
| char *err; |
| memset(line, '\0', sizeof(line)); |
| int len = read(fd, line, sizeof(line) - 1); |
| if (len <= 0) { |
| ret = false; |
| } else { |
| const long temp_value = strtol(line, &err, 10); |
| if (line[0] != '\0' && (*err == '\n' || *err == '\0')) { |
| *value = temp_value; |
| ret = true; |
| } |
| } |
| close(fd); |
| } |
| return ret; |
| } |
| |
| #if defined(ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY) |
| |
| // Reads a monotonic time source and returns a value in |
| // nanoseconds. The returned value uses an arbitrary epoch, not the |
| // Unix epoch. |
| static int64_t ReadMonotonicClockNanos() { |
| struct timespec t; |
| #ifdef CLOCK_MONOTONIC_RAW |
| int rc = clock_gettime(CLOCK_MONOTONIC_RAW, &t); |
| #else |
| int rc = clock_gettime(CLOCK_MONOTONIC, &t); |
| #endif |
| if (rc != 0) { |
| perror("clock_gettime() failed"); |
| abort(); |
| } |
| return int64_t{t.tv_sec} * 1000000000 + t.tv_nsec; |
| } |
| |
| class UnscaledCycleClockWrapperForInitializeFrequency { |
| public: |
| static int64_t Now() { return base_internal::UnscaledCycleClock::Now(); } |
| }; |
| |
| struct TimeTscPair { |
| int64_t time; // From ReadMonotonicClockNanos(). |
| int64_t tsc; // From UnscaledCycleClock::Now(). |
| }; |
| |
| // Returns a pair of values (monotonic kernel time, TSC ticks) that |
| // approximately correspond to each other. This is accomplished by |
| // doing several reads and picking the reading with the lowest |
| // latency. This approach is used to minimize the probability that |
| // our thread was preempted between clock reads. |
| static TimeTscPair GetTimeTscPair() { |
| int64_t best_latency = std::numeric_limits<int64_t>::max(); |
| TimeTscPair best; |
| for (int i = 0; i < 10; ++i) { |
| int64_t t0 = ReadMonotonicClockNanos(); |
| int64_t tsc = UnscaledCycleClockWrapperForInitializeFrequency::Now(); |
| int64_t t1 = ReadMonotonicClockNanos(); |
| int64_t latency = t1 - t0; |
| if (latency < best_latency) { |
| best_latency = latency; |
| best.time = t0; |
| best.tsc = tsc; |
| } |
| } |
| return best; |
| } |
| |
| // Measures and returns the TSC frequency by taking a pair of |
| // measurements approximately `sleep_nanoseconds` apart. |
| static double MeasureTscFrequencyWithSleep(int sleep_nanoseconds) { |
| auto t0 = GetTimeTscPair(); |
| struct timespec ts; |
| ts.tv_sec = 0; |
| ts.tv_nsec = sleep_nanoseconds; |
| while (nanosleep(&ts, &ts) != 0 && errno == EINTR) {} |
| auto t1 = GetTimeTscPair(); |
| double elapsed_ticks = t1.tsc - t0.tsc; |
| double elapsed_time = (t1.time - t0.time) * 1e-9; |
| return elapsed_ticks / elapsed_time; |
| } |
| |
| // Measures and returns the TSC frequency by calling |
| // MeasureTscFrequencyWithSleep(), doubling the sleep interval until the |
| // frequency measurement stabilizes. |
| static double MeasureTscFrequency() { |
| double last_measurement = -1.0; |
| int sleep_nanoseconds = 1000000; // 1 millisecond. |
| for (int i = 0; i < 8; ++i) { |
| double measurement = MeasureTscFrequencyWithSleep(sleep_nanoseconds); |
| if (measurement * 0.99 < last_measurement && |
| last_measurement < measurement * 1.01) { |
| // Use the current measurement if it is within 1% of the |
| // previous measurement. |
| return measurement; |
| } |
| last_measurement = measurement; |
| sleep_nanoseconds *= 2; |
| } |
| return last_measurement; |
| } |
| |
| #endif // ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY |
| |
| static double GetNominalCPUFrequency() { |
| long freq = 0; |
| |
| // Google's production kernel has a patch to export the TSC |
| // frequency through sysfs. If the kernel is exporting the TSC |
| // frequency use that. There are issues where cpuinfo_max_freq |
| // cannot be relied on because the BIOS may be exporting an invalid |
| // p-state (on x86) or p-states may be used to put the processor in |
| // a new mode (turbo mode). Essentially, those frequencies cannot |
| // always be relied upon. The same reasons apply to /proc/cpuinfo as |
| // well. |
| if (ReadLongFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", &freq)) { |
| return freq * 1e3; // Value is kHz. |
| } |
| |
| #if defined(ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY) |
| // On these platforms, the TSC frequency is the nominal CPU |
| // frequency. But without having the kernel export it directly |
| // though /sys/devices/system/cpu/cpu0/tsc_freq_khz, there is no |
| // other way to reliably get the TSC frequency, so we have to |
| // measure it ourselves. Some CPUs abuse cpuinfo_max_freq by |
| // exporting "fake" frequencies for implementing new features. For |
| // example, Intel's turbo mode is enabled by exposing a p-state |
| // value with a higher frequency than that of the real TSC |
| // rate. Because of this, we prefer to measure the TSC rate |
| // ourselves on i386 and x86-64. |
| return MeasureTscFrequency(); |
| #else |
| |
| // If CPU scaling is in effect, we want to use the *maximum* |
| // frequency, not whatever CPU speed some random processor happens |
| // to be using now. |
| if (ReadLongFromFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", |
| &freq)) { |
| return freq * 1e3; // Value is kHz. |
| } |
| |
| return 1.0; |
| #endif // !ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY |
| } |
| |
| #endif |
| |
| // InitializeSystemInfo() may be called before main() and before |
| // malloc is properly initialized, therefore this must not allocate |
| // memory. |
| static void InitializeSystemInfo() { |
| num_cpus = GetNumCPUs(); |
| nominal_cpu_frequency = GetNominalCPUFrequency(); |
| } |
| |
| int NumCPUs() { |
| base_internal::LowLevelCallOnce(&init_system_info_once, InitializeSystemInfo); |
| return num_cpus; |
| } |
| |
| double NominalCPUFrequency() { |
| base_internal::LowLevelCallOnce(&init_system_info_once, InitializeSystemInfo); |
| return nominal_cpu_frequency; |
| } |
| |
| #if defined(_WIN32) |
| |
| pid_t GetTID() { |
| return GetCurrentThreadId(); |
| } |
| |
| #elif defined(__linux__) |
| |
| #ifndef SYS_gettid |
| #define SYS_gettid __NR_gettid |
| #endif |
| |
| pid_t GetTID() { |
| return syscall(SYS_gettid); |
| } |
| |
| #elif defined(__akaros__) |
| |
| pid_t GetTID() { |
| // Akaros has a concept of "vcore context", which is the state the program |
| // is forced into when we need to make a user-level scheduling decision, or |
| // run a signal handler. This is analogous to the interrupt context that a |
| // CPU might enter if it encounters some kind of exception. |
| // |
| // There is no current thread context in vcore context, but we need to give |
| // a reasonable answer if asked for a thread ID (e.g., in a signal handler). |
| // Thread 0 always exists, so if we are in vcore context, we return that. |
| // |
| // Otherwise, we know (since we are using pthreads) that the uthread struct |
| // current_uthread is pointing to is the first element of a |
| // struct pthread_tcb, so we extract and return the thread ID from that. |
| // |
| // TODO(dcross): Akaros anticipates moving the thread ID to the uthread |
| // structure at some point. We should modify this code to remove the cast |
| // when that happens. |
| if (in_vcore_context()) |
| return 0; |
| return reinterpret_cast<struct pthread_tcb *>(current_uthread)->id; |
| } |
| |
| #elif defined(__myriad2__) |
| |
| pid_t GetTID() { |
| uint32_t tid; |
| rtems_task_ident(RTEMS_SELF, 0, &tid); |
| return tid; |
| } |
| |
| #else |
| |
| // Fallback implementation of GetTID using pthread_getspecific. |
| static once_flag tid_once; |
| static pthread_key_t tid_key; |
| static absl::base_internal::SpinLock tid_lock( |
| absl::base_internal::kLinkerInitialized); |
| |
| // We set a bit per thread in this array to indicate that an ID is in |
| // use. ID 0 is unused because it is the default value returned by |
| // pthread_getspecific(). |
| static std::vector<uint32_t>* tid_array GUARDED_BY(tid_lock) = nullptr; |
| static constexpr int kBitsPerWord = 32; // tid_array is uint32_t. |
| |
| // Returns the TID to tid_array. |
| static void FreeTID(void *v) { |
| intptr_t tid = reinterpret_cast<intptr_t>(v); |
| int word = tid / kBitsPerWord; |
| uint32_t mask = ~(1u << (tid % kBitsPerWord)); |
| absl::base_internal::SpinLockHolder lock(&tid_lock); |
| assert(0 <= word && static_cast<size_t>(word) < tid_array->size()); |
| (*tid_array)[word] &= mask; |
| } |
| |
| static void InitGetTID() { |
| if (pthread_key_create(&tid_key, FreeTID) != 0) { |
| // The logging system calls GetTID() so it can't be used here. |
| perror("pthread_key_create failed"); |
| abort(); |
| } |
| |
| // Initialize tid_array. |
| absl::base_internal::SpinLockHolder lock(&tid_lock); |
| tid_array = new std::vector<uint32_t>(1); |
| (*tid_array)[0] = 1; // ID 0 is never-allocated. |
| } |
| |
| // Return a per-thread small integer ID from pthread's thread-specific data. |
| pid_t GetTID() { |
| absl::call_once(tid_once, InitGetTID); |
| |
| intptr_t tid = reinterpret_cast<intptr_t>(pthread_getspecific(tid_key)); |
| if (tid != 0) { |
| return tid; |
| } |
| |
| int bit; // tid_array[word] = 1u << bit; |
| size_t word; |
| { |
| // Search for the first unused ID. |
| absl::base_internal::SpinLockHolder lock(&tid_lock); |
| // First search for a word in the array that is not all ones. |
| word = 0; |
| while (word < tid_array->size() && ~(*tid_array)[word] == 0) { |
| ++word; |
| } |
| if (word == tid_array->size()) { |
| tid_array->push_back(0); // No space left, add kBitsPerWord more IDs. |
| } |
| // Search for a zero bit in the word. |
| bit = 0; |
| while (bit < kBitsPerWord && (((*tid_array)[word] >> bit) & 1) != 0) { |
| ++bit; |
| } |
| tid = (word * kBitsPerWord) + bit; |
| (*tid_array)[word] |= 1u << bit; // Mark the TID as allocated. |
| } |
| |
| if (pthread_setspecific(tid_key, reinterpret_cast<void *>(tid)) != 0) { |
| perror("pthread_setspecific failed"); |
| abort(); |
| } |
| |
| return static_cast<pid_t>(tid); |
| } |
| |
| #endif |
| |
| } // namespace base_internal |
| } // namespace absl |