// 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 // // https://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 #include #else #include #include #include #include #include #endif #ifdef __linux__ #include #endif #if defined(__APPLE__) || defined(__FreeBSD__) #include #endif #if defined(__myriad2__) #include #endif #include #include #include #include #include #include #include #include // NOLINT(build/c++11) #include #include #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 { inline namespace lts_2019_08_08 { 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(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::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(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* 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(v); int word = tid / kBitsPerWord; uint32_t mask = ~(1u << (tid % kBitsPerWord)); absl::base_internal::SpinLockHolder lock(&tid_lock); assert(0 <= word && static_cast(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(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(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(tid)) != 0) { perror("pthread_setspecific failed"); abort(); } return static_cast(tid); } #endif } // namespace base_internal } // inline namespace lts_2019_08_08 } // namespace absl