mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-11-02 03:40:12 +00:00
407 lines
12 KiB
C++
407 lines
12 KiB
C++
// 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 {
|
|
inline namespace lts_2018_12_18 {
|
|
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);
|
|
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) && defined(_MSC_VER)
|
|
|
|
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.
|
|
// this might work on mingw32
|
|
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
|
|
} // inline namespace lts_2018_12_18
|
|
} // namespace absl
|