383 lines
12 KiB
C++
383 lines
12 KiB
C++
#if defined(__linux__)
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <clocale>
|
|
#include <csignal>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <dlfcn.h>
|
|
#include <execinfo.h>
|
|
#include <fcntl.h>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <limits.h>
|
|
#include <map>
|
|
#include <pwd.h>
|
|
#include <regex>
|
|
#include <sched.h>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <sys/prctl.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
#include "export.h"
|
|
#if __has_include("Core/JSON.h")
|
|
#include "Core/JSON.h"
|
|
#elif __has_include("../Base/JSON.h")
|
|
#include "../Base/JSON.h"
|
|
#endif
|
|
namespace Psc {
|
|
namespace {
|
|
std::string trim_copy(std::string_view value) {
|
|
auto not_space = [](unsigned char ch) {
|
|
return !std::isspace(ch);
|
|
};
|
|
auto first = std::find_if(value.begin(), value.end(), not_space);
|
|
if (first == value.end()) {
|
|
return {};
|
|
}
|
|
auto last = std::find_if(value.rbegin(), value.rend(), not_space).base();
|
|
return std::string(first, last);
|
|
}
|
|
std::string read_first_line(std::string_view file_name) {
|
|
std::ifstream in(file_name.data());
|
|
std::string line;
|
|
if (std::getline(in, line)) {
|
|
return trim_copy(line);
|
|
}
|
|
return "";
|
|
}
|
|
std::string execute_command_single(const char* cmd) {
|
|
if (!cmd || !*cmd) {
|
|
return "";
|
|
}
|
|
int pipefd[2]{};
|
|
if (pipe(pipefd) != 0) {
|
|
return "";
|
|
}
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
close(pipefd[0]);
|
|
close(pipefd[1]);
|
|
return "";
|
|
}
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
dup2(pipefd[1], STDOUT_FILENO);
|
|
close(pipefd[1]);
|
|
setsid();
|
|
execl("/bin/sh", "sh", "-c", cmd, static_cast<char*>(nullptr));
|
|
_exit(127);
|
|
}
|
|
close(pipefd[1]);
|
|
std::string out;
|
|
char buf[1024];
|
|
ssize_t n = 0;
|
|
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
|
|
out.append(buf, static_cast<size_t>(n));
|
|
}
|
|
close(pipefd[0]);
|
|
waitpid(pid, nullptr, 0);
|
|
return out;
|
|
}
|
|
size_t read_proc_status_kb(const char* key) {
|
|
std::ifstream status("/proc/self/status");
|
|
std::string line;
|
|
while (std::getline(status, line)) {
|
|
if (line.rfind(key, 0) == 0) {
|
|
size_t value_kb = 0;
|
|
if (std::sscanf(line.c_str(), "%*s %zu kB", &value_kb) == 1) {
|
|
return value_kb;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
} // namespace
|
|
std::string extract_address(std::string_view input) {
|
|
static const std::regex address_regex(R"(\[([0-9a-fA-Fx]+)\])");
|
|
std::match_results<std::string_view::const_iterator> match;
|
|
if (std::regex_search(input.begin(), input.end(), match, address_regex)) {
|
|
return match[1].str();
|
|
}
|
|
return {};
|
|
}
|
|
std::string execute_command(std::string_view cmd) {
|
|
FILE* fp = popen(cmd.data(), "r");
|
|
if (!fp) {
|
|
return "";
|
|
}
|
|
std::string result;
|
|
char buffer[1024];
|
|
while (fgets(buffer, sizeof(buffer), fp) != nullptr) {
|
|
result.append(buffer);
|
|
}
|
|
pclose(fp);
|
|
return result;
|
|
}
|
|
std::string get_tool(std::string_view _name) {
|
|
std::string name(_name);
|
|
std::string ret = name;
|
|
const std::string exe_dir = get_exe_dir();
|
|
const std::string ret_1 = exe_dir + "/" + name;
|
|
std::filesystem::path path = std::filesystem::u8path(exe_dir);
|
|
const auto parent = path.parent_path();
|
|
const std::string parent_path = path_to_utf8(parent);
|
|
const std::string ret_2 = parent_path + "/lib/" + name;
|
|
if (std::filesystem::exists(ret_1)) {
|
|
ret = ret_1;
|
|
}
|
|
else if (std::filesystem::exists(ret_2)) {
|
|
ret = ret_2;
|
|
}
|
|
return ret;
|
|
}
|
|
std::string get_stack_trace() {
|
|
std::ostringstream oss;
|
|
constexpr int max_frames = 64;
|
|
void* stack[max_frames]{};
|
|
const int stack_size = backtrace(stack, max_frames);
|
|
char** symbols = backtrace_symbols(stack, stack_size);
|
|
if (!symbols) {
|
|
oss << "Error retrieving stack symbols\n";
|
|
return oss.str();
|
|
}
|
|
const std::string addr2line = get_tool("addr2line");
|
|
const std::string cppfilt = get_tool("c++filt");
|
|
const std::string exe = get_exe_path();
|
|
oss << "addr2line: " << addr2line << '\n';
|
|
oss << "cppfilt: " << cppfilt << '\n';
|
|
oss << "Stack trace: " << exe << '\n';
|
|
for (int i = 0; i < stack_size; ++i) {
|
|
const auto addr = reinterpret_cast<std::uintptr_t>(stack[i]);
|
|
oss << "【" << std::dec << std::left << std::setw(2) << i << "】";
|
|
char cmd[4096];
|
|
std::snprintf(cmd, sizeof(cmd), R"(%s -e "%s" -f -i 0x%lx | %s)",
|
|
addr2line.c_str(), exe.c_str(),
|
|
static_cast<unsigned long>(addr), cppfilt.c_str());
|
|
std::string str = execute_command_single(cmd);
|
|
if (str.empty() && symbols[i]) {
|
|
str = symbols[i];
|
|
}
|
|
std::replace(str.begin(), str.end(), '\n', ' ');
|
|
oss << " " << str << "【0x" << std::hex << addr << std::dec << "】"
|
|
<< '\n';
|
|
}
|
|
free(symbols);
|
|
return oss.str();
|
|
}
|
|
std::string get_computer_serial_number() {
|
|
static const char* candidates[] = {
|
|
"/sys/class/dmi/id/product_serial", "/sys/class/dmi/id/board_serial",
|
|
"/sys/class/dmi/id/product_uuid", "/sys/class/dmi/id/chassis_serial"};
|
|
for (const char* file : candidates) {
|
|
std::string value = read_first_line(file);
|
|
if (!value.empty() && value != "None" && value != "Not Specified" &&
|
|
value != "To Be Filled By O.E.M.") {
|
|
return value;
|
|
}
|
|
}
|
|
return "Unknown";
|
|
}
|
|
void set_current_thread_name(std::string_view name) {
|
|
auto trimmed =
|
|
name.substr(0, 15); // Linux 线程名最多 16 字节,含结尾 \0。
|
|
(void)pthread_setname_np(pthread_self(), trimmed.data());
|
|
}
|
|
void set_console_utf8() {
|
|
if (setlocale(LC_ALL, "") != nullptr) {
|
|
return;
|
|
}
|
|
if (setlocale(LC_ALL, "C.UTF-8") != nullptr) {
|
|
return;
|
|
}
|
|
(void)setlocale(LC_ALL, "en_US.UTF-8");
|
|
}
|
|
PSC_DEFINE_TRIPLE_API_FROM_BOOL(void*, load_library, PSC_LOAD_LIBRARY_PARAMS)
|
|
bool load_library_ec(std::string_view library_path, void*& out,
|
|
std::error_code& ec) noexcept {
|
|
out = nullptr;
|
|
ec.clear();
|
|
(void)dlerror();
|
|
void* library = dlopen(library_path.data(), RTLD_LAZY);
|
|
if (!library) {
|
|
const char* err = dlerror();
|
|
std::cerr << library_path
|
|
<< " Failed to load library: " << (err ? err : "unknown") << '\n';
|
|
ec = std::make_error_code(std::errc::no_such_file_or_directory);
|
|
return false;
|
|
}
|
|
out = library;
|
|
return true;
|
|
}
|
|
void* load_library_fail_fast(std::string_view library_path) {
|
|
auto ret = try_load_library(library_path);
|
|
if (!ret) {
|
|
Psc::fail_fast();
|
|
}
|
|
return ret.value();
|
|
}
|
|
void free_library(void* library) {
|
|
if (library) {
|
|
dlclose(library);
|
|
}
|
|
}
|
|
PSC_DEFINE_TRIPLE_API_FROM_BOOL(void*, load_function, PSC_LOAD_FUNCTION_PARAMS)
|
|
bool load_function_ec(void* library, std::string_view function_name,
|
|
void*& out, std::error_code& ec) noexcept {
|
|
out = nullptr;
|
|
ec.clear();
|
|
if (!library) {
|
|
std::cerr << function_name << " function not loaded (library == nullptr)\n";
|
|
ec = std::make_error_code(std::errc::invalid_argument);
|
|
return false;
|
|
}
|
|
(void)dlerror();
|
|
void* func = dlsym(library, function_name.data());
|
|
const char* err = dlerror();
|
|
if (err != nullptr) {
|
|
std::cerr << "dlsym failed for " << function_name << " : " << err << '\n';
|
|
ec = std::make_error_code(std::errc::function_not_supported);
|
|
return false;
|
|
}
|
|
out = func;
|
|
return true;
|
|
}
|
|
void* load_function_fail_fast(void* library, std::string_view function_name) {
|
|
auto ret = try_load_function(library, function_name);
|
|
if (!ret) {
|
|
Psc::fail_fast();
|
|
}
|
|
return ret.value();
|
|
}
|
|
std::string get_error_message() {
|
|
const int ec = errno;
|
|
return "错误码:" + std::to_string(ec) + ": " + std::strerror(ec);
|
|
}
|
|
std::string get_error_message(ERROR_CODE_TYPE ec) {
|
|
return "错误码:" + std::to_string(ec) + ": " +
|
|
std::strerror(static_cast<int>(ec));
|
|
}
|
|
ERROR_CODE_TYPE get_error_code() {
|
|
return errno;
|
|
}
|
|
void set_error_code(ERROR_CODE_TYPE code) {
|
|
errno = static_cast<int>(code);
|
|
}
|
|
std::string get_exe_path() {
|
|
std::vector<char> buffer(PATH_MAX + 1, '\0');
|
|
const ssize_t len =
|
|
readlink("/proc/self/exe", buffer.data(), buffer.size() - 1);
|
|
if (len == -1) {
|
|
return "";
|
|
}
|
|
buffer[static_cast<size_t>(len)] = '\0';
|
|
return std::string(buffer.data());
|
|
}
|
|
std::string get_home_dir() {
|
|
if (const char* home = std::getenv("HOME")) {
|
|
return home;
|
|
}
|
|
struct passwd* pw = getpwuid(getuid());
|
|
if (pw && pw->pw_dir) {
|
|
return std::string(pw->pw_dir);
|
|
}
|
|
return "";
|
|
}
|
|
bool set_thread_priority(void* thread_ptr, int priority, bool realtime) {
|
|
if (!thread_ptr) {
|
|
return false;
|
|
}
|
|
pthread_t pthread = *static_cast<pthread_t*>(thread_ptr);
|
|
int policy = realtime ? SCHED_FIFO : SCHED_OTHER;
|
|
sched_param param{};
|
|
if (realtime) {
|
|
const int min_prio = sched_get_priority_min(policy);
|
|
const int max_prio = sched_get_priority_max(policy);
|
|
if (min_prio >= 0 && max_prio >= min_prio) {
|
|
priority = std::max(min_prio, std::min(max_prio, priority));
|
|
}
|
|
param.sched_priority = priority;
|
|
}
|
|
else {
|
|
param.sched_priority = 0;
|
|
}
|
|
const int ret = pthread_setschedparam(pthread, policy, ¶m);
|
|
if (ret != 0) {
|
|
errno = ret;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool set_thread_priority(std::thread* thread, int priority, bool realtime) {
|
|
if (!thread) {
|
|
return false;
|
|
}
|
|
pthread_t pthread = thread->native_handle();
|
|
return set_thread_priority(static_cast<void*>(&pthread), priority, realtime);
|
|
}
|
|
bool set_thread_affinity(void* thread_ptr, unsigned cpu_index) {
|
|
if (!thread_ptr) {
|
|
return false;
|
|
}
|
|
if (cpu_index >= CPU_SETSIZE) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
pthread_t pthread = *static_cast<pthread_t*>(thread_ptr);
|
|
cpu_set_t cpuset;
|
|
CPU_ZERO(&cpuset);
|
|
CPU_SET(cpu_index, &cpuset);
|
|
const int ret = pthread_setaffinity_np(pthread, sizeof(cpu_set_t), &cpuset);
|
|
if (ret != 0) {
|
|
errno = ret;
|
|
std::cerr << "Error setting thread affinity: " << std::strerror(ret)
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool set_thread_affinity(std::thread* thread, unsigned cpu_index) {
|
|
if (!thread) {
|
|
return false;
|
|
}
|
|
pthread_t pthread = thread->native_handle();
|
|
return set_thread_affinity(static_cast<void*>(&pthread), cpu_index);
|
|
}
|
|
std::string get_memory_info() {
|
|
auto ret = JSON::object();
|
|
ret.append({"VmRSS (MB)", read_proc_status_kb("VmRSS:") / 1024});
|
|
ret.append({"VmSize (MB)", read_proc_status_kb("VmSize:") / 1024});
|
|
ret.append({"VmPeak (MB)", read_proc_status_kb("VmPeak:") / 1024});
|
|
ret.append({"VmHWM (MB)", read_proc_status_kb("VmHWM:") / 1024});
|
|
return ret.to_json_string();
|
|
}
|
|
size_t get_system_memory() {
|
|
std::ifstream meminfo("/proc/meminfo");
|
|
std::string line;
|
|
while (std::getline(meminfo, line)) {
|
|
if (line.rfind("MemTotal:", 0) == 0) {
|
|
size_t mem_total_kb = 0;
|
|
if (std::sscanf(line.c_str(), "MemTotal: %zu kB", &mem_total_kb) == 1) {
|
|
return mem_total_kb * 1024;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
size_t get_process_memory() {
|
|
return read_proc_status_kb("VmRSS:") * 1024;
|
|
}
|
|
} // namespace Psc
|
|
#endif // defined(__linux__)
|