src/dxapi/native/platform/platform.cpp (585 lines of code) (raw):
/*
* Copyright 2021 EPAM Systems, Inc
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. 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 "tickdb/common.h"
#include "util/critical_section.h"
#include <exception>
#include <ctime>
#if DX_PLATFORM_WINDOWS
#include "windows/resource.h"
#include "windows/winplat_impl.h"
#include "sockets_impl.h"
#endif
#if !defined(_MSC_VER)
#define _open open
#define _close close
#define _write write
#define _strdup strdup
#ifndef O_TEXT
#define O_TEXT 0
#endif
#endif // !defined(_MSC_VER)
NOINLINE size_t strlen_ni(const char *s)
{
return strlen(s);
}
NOINLINE int strcmp_ni(const char *str_a, const char * str_b)
{
return strcmp(str_a, str_b);
}
NOINLINE void * memset_ni(void *data, int value, size_t size)
{
return memset(data, value, size);
}
static bool strhasprefix_impl(const char *s, const char *prefix, size_t l1, size_t l2)
{
if (NULL == s || NULL == prefix) {
return false;
}
if (l1 < l2) {
return false;
}
return s == prefix || 0 == memcmp(s, prefix, l2);
}
bool strhasprefix(const char *s, const char *prefix)
{
return strhasprefix_impl(s, prefix, strlen(s), strlen(prefix));
}
bool strhasprefix(const char *s, size_t string_length, const char *prefix, size_t prefixlength)
{
return strhasprefix_impl(s, prefix, string_length, prefixlength);
}
bool strhasprefix(const char *s, const char *prefix, size_t prefixlength)
{
return strhasprefix_impl(s, prefix, strlen(s), prefixlength);
}
// Based on stbiw__crc32 from stb_image_write by Sean Barrett
uint32_t crc32(const uint8_t *buffer, size_t len)
{
static uint32_t crc_table[256];
if (0 == crc_table[1]) {
forn (i, 256) {
uint32_t crc = (uint32_t)i;
forn (j, 8) {
crc = (crc >> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
}
crc_table[i] = crc;
}
}
uint32_t crc = ~0u;
buffer += len;
for_neg (i, -(intptr_t)len) {
crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xFF)];
}
return ~crc;
}
// Split source string into up to n strings, using the specified separator character, by copying them into an array of std::string
// Returns the number of strings
size_t split(std::string strings[], size_t nstrings, const char *src, char separator)
{
if (NULL == src || NULL == strings || nstrings <= 0)
return 0;
assert(strings->data() != src);
size_t i;
for(i = 1; i != nstrings; ++i) {
const char * p = strchr(src, (unsigned char)separator);
if (NULL == p)
break;
(strings++)->assign(src, p);
src = p + 1;
}
strings->assign(src);
return i;
}
// In-place replace
NOINLINE void replace_all(char *s, size_t n, const char *from, const char *to)
{
if ('\0' != s[n]) {
throw std::logic_error("replace_all - string must be 0-terminated!");
}
size_t len = strlen_ni(from);
if (strlen_ni(to) != len) {
throw std::invalid_argument("replace: length(from) != length(to)");
}
while (NULL != (s = strstr(s, from))) {
memcpy(s, to, len);
s += len;
}
}
NOINLINE void replace_all(std::string * const dest, const char *src, const char *from, const char *to)
{
if (NULL == dest || NULL == src || NULL == from || NULL == to) {
throw std::invalid_argument("one of the arguments is NULL");
}
size_t from_len = strlen_ni(from);
dest->clear();
while ('\0' != (*src)) {
if (0 == strncmp(src, from, from_len)) {
dest->append(to);
src += from_len;
}
else {
dest->push_back(*src++);
}
}
}
bool dbg_log_enabled = DBG_LOG_ENABLED;
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static int dbgfile__ = -1;
static char *log_file_base;
static QPCTimer log_qpc_timer;
static uint64_t log_timer_base_us;
// High-performance unix time in microseconds
static uint64_t timestamp_us()
{
static dx_thread::critical_section section;
if (0 == log_timer_base_us) {
dx_thread::yield_lock lock(section);
log_qpc_timer.init(0);
log_timer_base_us = time_us() - log_qpc_timer.getMicros();
}
return log_qpc_timer.getMicros() + log_timer_base_us;
}
static struct tm* localtime(uint64_t timestampSeconds)
{
time_t timeint = timestampSeconds;
return localtime(&timeint);
}
static struct tm* gmtime(uint64_t timestampSeconds)
{
time_t timeint = timestampSeconds;
return gmtime(&timeint);
}
INLINE static struct tm* localtime_from_us(uint64_t timestampMks)
{
return localtime(timestampMks / 1000000U);
}
INLINE static struct tm* gmtime_from_us(uint64_t timestampMks)
{
return gmtime(timestampMks / 1000000U);
}
INLINE static struct tm * gmtime_from_ms(uint64_t timestampMs)
{
return gmtime(timestampMs / 1000U);
}
INLINE static struct tm * gmtime_from_ns(uint64_t timestampNs)
{
return gmtime(timestampNs / 1000000000U);
}
static unsigned dbg_get_pid()
{
return (unsigned)
#if DX_PLATFORM_WINDOWS
GetCurrentProcessId();
#else
getpid();
#endif
}
NOINLINE std::string dbg_logfile_path(const char * name, const char * ext)
{
std::string out;
static dx_thread::critical_section section;
if (NULL == log_file_base) {
dx_thread::yield_lock lock(section);
char filename[0x800];
const char *root;
unsigned pid = dbg_get_pid();
root = getenv("DXAPI_LOG_PATH");
#if DX_PLATFORM_WINDOWS
if (NULL == root) {
root = getenv("TEMP");
}
#else
if (NULL == root) {
root = ".";
}
#endif
if (NULL != root && strlen_ni(root) < sizeof(filename) - 0x20) {
auto t = localtime_from_us(timestamp_us());
if (NULL != t) {
snprintf(filename, sizeof(filename), "%s/" LOG_FILE_NAME_PREFIX "%02d%02d_%02d%02d%02d_%05u_", root,
t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
pid);
}
else {
snprintf(filename, sizeof(filename), "%s/" LOG_FILE_NAME_PREFIX "%05u_", root, pid);
}
log_file_base = _strdup(filename);
}
if (NULL == log_file_base) {
dbg_log_enabled = false;
}
}
// Do not remove this comparison
if (NULL != log_file_base) {
out.append(log_file_base);
out.append(name);
out.append(".");
out.append(ext);
}
return out;
}
#define HLINE "------------------------------------------------------------------------------"
NOINLINE static bool dbg_open_logfile(const char * fname, int flags)
{
if (-1 != dbgfile__) {
_close(dbgfile__);
}
dbgfile__ = _open(fname, O_APPEND | O_TEXT | O_WRONLY | O_TRUNC | flags, 00666);
#if defined(DEBUG) || defined(_DEBUG)
#define CONFIGURATION "Debug"
#else
#define CONFIGURATION "Release"
#endif
if (-1 != dbgfile__) {
#if DX_PLATFORM_WINDOWS
char exename[0x1000];
char libname[0x1000];
GetModuleFileNameA(NULL, exename, sizeof(exename));
exename[sizeof(exename) - 1] = '\0';
libname[0] = '\0';
GetModuleFileNameA(GetModuleHandleA(VER_INTERNALNAME_STR), libname, sizeof(libname));
libname[sizeof(libname) - 1] = '\0';
dbg_log(LOG_START_MSG "\n%s\n%s\n%s\n"
"Version : %s " CONFIGURATION "\n"
"Built : " __DATE__ " " __TIME__ "\n"
// "CLR ver.: " __CLR_VER "\n"
"MSC ver. : %u\n"
"PID : %u\n" HLINE, exename, GetCommandLineA(), libname,
VER_FILEVERSION_STR " " VER_BUILD_CONFIGURATION_STR, _MSC_FULL_VER,
dbg_get_pid());
#else
dbg_log(LOG_START_MSG "\n"
"Version : " CONFIGURATION "\n"
"Built : " __DATE__ " " __TIME__ "\n"
"PID : %u\n" HLINE,
dbg_get_pid());
#endif
}
return -1 != dbgfile__;
}
NOINLINE static bool auto_configure_logger()
{
// TODO: remove
#if DX_PLATFORM_WINDOWS
char path[0x1000], *p;
size_t len;
GetModuleFileNameA(NULL, path, sizeof(path) - 0x100);
len = strlen_ni(path);
p = path + len - 1;
// Decrement pointer until start of path or start of the string
for (; p >= path && *p != '\\' && *p != '/'; --p)
if (p < path) {
return false;
}
memcpy(++p, LOG_FILE_NAME_PREFIX LOG_FILE_NAME_SUFFIX "." LOG_FILE_EXT, strlen(LOG_FILE_NAME_PREFIX LOG_FILE_NAME_SUFFIX "." LOG_FILE_EXT) + 1);
return dbg_open_logfile(path, dbg_log_enabled ? O_CREAT : 0);
#else
return false;
#endif
};
INLINE static void dbg_log(const char * text, uintptr length)
{
static dx_thread::critical_section section;
assert(-1 != dbgfile__);
{
dx_thread::yield_lock lock(section);
char timebuf[0x80];
static bool first_record = true;
uint64_t microtime = timestamp_us();
struct tm * tstruct = localtime_from_us(microtime);
if (NULL != tstruct) {
if (first_record) {
first_record = false;
snprintf(timebuf, sizeof(timebuf), /*"%04d-" */ "%02d-%02d ", /*1900 + tstruct->tm_year, */ tstruct->tm_mon + 1, tstruct->tm_mday);
_write(dbgfile__, timebuf, 11 - 5);
}
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06llu ", tstruct->tm_hour, tstruct->tm_min, tstruct->tm_sec, (ulonglong)(microtime % 1000000U));
_write(dbgfile__, timebuf, 16);
}
_write(dbgfile__, text, (unsigned)length);
_write(dbgfile__, "\n", 1);
}
}
static class Helper123123 {
public:
Helper123123()
{
// First, attempt to open the log file in local directory
//if (auto_configure_logger()) {
// dbg_log_enabled = true;
// return;
// }
if (dbg_log_enabled) {
dbg_log_enabled = dbg_open_logfile(dbg_logfile_path(LOG_FILE_NAME_SUFFIX, LOG_FILE_EXT).c_str(), O_CREAT);
}
}
~Helper123123()
{
if (-1 != dbgfile__) {
dbg_log("\n--closed--");
_close(dbgfile__);
}
if (log_file_base != NULL) {
::free(log_file_base);
log_file_base = NULL;
}
}
} hlp3123;
static INLINE size_t dbg_va_format(char buffer[], const size_t bufferSize, const char *text, va_list p)
{
int ret;
// TODO: Check and use the return value, considering the MSVC incompatibilities
// refer to tinyxml2 implemenation
ret = vsnprintf(buffer, bufferSize, text, p);
buffer[bufferSize - 1] = '\0';
size_t len = strlen(buffer);
return len;
}
static INLINE size_t dbg_va_format(std::string *out, const char *text, va_list va, size_t ofs,
char buffer[], const size_t buffer_size)
{
size_t len = dbg_va_format(buffer, buffer_size, text, va);
if (NULL != out) {
size_t size = out->size();
ofs = ofs > size ? size : ofs;
out->resize(len + ofs);
memcpy(&((*out)[ofs]), buffer, len);
}
return len;
}
static INLINE size_t dbg_va_format(std::string *out, const char *text, va_list va, size_t ofs)
{
char buffer[0x10000];
return dbg_va_format(out, text, va, ofs, buffer, sizeof(buffer));
}
static INLINE void dbg_va_log(std::string *out, const char *text, va_list va, size_t ofs)
{
char buffer[0x10000];
size_t len = dbg_va_format(out, text, va, ofs, buffer, sizeof(buffer));
dbg_log(buffer, len);
}
NOINLINE void dbg_log(const char *fmt, ...)
{
VA_DELEGATE(va, fmt, dbg_va_log(NULL, fmt, va, 0));
}
NOINLINE void dbg_log(std::string *out, const char *fmt, ...)
{
VA_DELEGATE(va, fmt, dbg_va_log(out, fmt, va, 0));
}
NOINLINE void format_string(std::string *out, const char *fmt, ...)
{
VA_DELEGATE(va, fmt, dbg_va_format(out, fmt, va, 0));
}
NOINLINE void format_string(std::string *out, size_t offset, const char *fmt, ...)
{
VA_DELEGATE(va, fmt, dbg_va_format(out, fmt, va, offset));
}
NOINLINE void format_string(std::string *out, const char *fmt, va_list va)
{
dbg_va_format(out, fmt, va, 0);
}
NOINLINE void format_string(std::string *out, size_t offset, const char *fmt, va_list va)
{
dbg_va_format(out, fmt, va, offset);
}
NOINLINE void dbg_dump(const char *basename, void *data, size_t data_size)
{
static dx_thread::critical_section section;
static uint64_t counter;
char name[0x400];
{
dx_thread::yield_lock lock(section);
snprintf(name, sizeof(name), "%s-%06llu", basename, (ulonglong)counter++);
name[sizeof(name) - 1] = '\0';
std::string path = dbg_logfile_path(name, "bin");
FILE *f = fopen(path.c_str(), "wb");
if (NULL != f) {
fwrite(data, 1, data_size, f);
fclose(f);
}
}
}
// TODO: move to utils or something
bool load_file(std::vector<uint8_t> &out, const char *filename)
{
FILE *f = fopen((const char*)filename, "rb");
if (NULL == f) {
dbg_log("ERROR: Unable to open file: %s\n", filename);
return false;
}
/* Read the whole file contents into buffer */
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
out.resize(size);
if (size >= SIZE_MAX) {
fclose(f);
dbg_log("ERROR: File %s is too big : %llu bytes\n", filename, (ulonglong)size);
return false;
}
rewind(f);
size_t sizeread = fread(&out[0], 1, size, f);
fclose(f);
if (size != sizeread) {
out.resize(sizeread);
dbg_log("ERROR: Unable to read file: %s, %llu/%llu read\n", filename,
(ulonglong)sizeread, (ulonglong)size);
return false;
}
return true;
}
/**
* Format error message to a string buffer from system-dependent eror code.
* For non-windows systems errno is expected
* will always 0-terminate (last character written is '\0', but not included in the returned length)
* so, the returned value is [0 .. dst_size-1]
*/
size_t format_error(char *dst, size_t dst_size, int error)
{
size_t n_appended;
// dst_size expected to be 1..0xFFFFFFFF
if (dst_size - 1 >= UINT32_MAX)
return 0;
#ifdef _WIN32
n_appended = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
dst, (DWORD)(dst_size - 1), NULL);
/* Can return 0 on error, but that's ok, we return empty line then, instead of trying
to format error message for the function that formats error message
*/
#else
const char *strerr = strerror(error);
n_appended = strlen(strerr);
if (n_appended >= dst_size) {
n_appended = dst_size - 1;
}
memcpy(dst, strerr, n_appended);
#endif
assert(n_appended < dst_size);
dst[n_appended] = '\0';
return n_appended;
}
size_t format_error(std::string *out, size_t max_append_size, int error)
{
assert(out != NULL);
size_t start = out->size();
out->resize(start + max_append_size);
size_t n_appended = format_error(&(*out)[start], max_append_size, error);
out->resize(start + n_appended);
return n_appended;
}
std::string format_socket_error(int error, const char *source)
{
std::string out;
char txt[200];
if (NULL != source) {
out.append(source).append(": ");
}
snprintf(txt, sizeof(txt), "Socket error: #%d - ", error);
out.append(txt);
format_error(&out, 0x400, error);
return out;
}
std::string last_socket_error(int *errorcode, const char *source)
{
#ifdef _WIN32
int error = WSAGetLastError();
#else
int error = errno;
#endif
if (NULL != errorcode) {
*errorcode = error;
}
return format_socket_error(error, source);
}
// TODO: move this
#define TIMESTAMP_NULL INT64_MIN
#define USE_CURRENT_TIME (TIMESTAMP_NULL + 1)
#define USE_CURSOR_TIME (TIMESTAMP_NULL + 2)
const char * timestamp_const_str(int64_t x)
{
if (TIMESTAMP_NULL == x) {
return "<*>";
}
else if (USE_CURSOR_TIME == x) {
return "<CURSOR_TIME>";
}
else if (USE_CURRENT_TIME == x) {
return "<CURRENT_TIME>";
}
else {
return NULL;
}
}
std::string timestamp_cursor2str(int64_t x)
{
std::string out;
char timebuf[80];
const char *cs = timestamp_const_str(x);
if (NULL != cs) {
out.assign(cs);
}
else {
struct tm *tstruct = gmtime_from_us(x * 1000);
if (NULL != tstruct) {
snprintf(timebuf, sizeof(timebuf),
/*"%04d-" */ "%02d-%02d "
"%02d:%02d:%02d.%03llu",
/*1900 + tstruct->tm_year, */ tstruct->tm_mon + 1, tstruct->tm_mday,
tstruct->tm_hour, tstruct->tm_min, tstruct->tm_sec, (ulonglong)(x % 1000U));
out.assign(timebuf);
}
else {
out.assign("<ERROR>");
}
}
return out;
}
std::string timestamp_ns2str(int64_t x, bool withSeparator)
{
std::string out;
char timebuf[80];
const char *cs = timestamp_const_str(x);
if (NULL != cs) {
out.assign(cs);
}
else {
struct tm *tstruct = gmtime_from_ns(x);
if (NULL != tstruct) {
static const char *fmtStrings[] = { "%02d%02d%02d'%06llu", "%02d:%02d:%02d.%06llu" };
snprintf(timebuf, sizeof(timebuf), fmtStrings[withSeparator], tstruct->tm_hour, tstruct->tm_min, tstruct->tm_sec, (ulonglong)(x / 1000U % 1000000U));
out.assign(timebuf);
}
else {
out.assign("<ERROR>");
}
}
return out;
}
void sleep_ns(int64_t sleep_ns)
{
if (sleep_ns < 0) {
sleep_ns = 0;
}
#if DX_PLATFORM_WINDOWS
Sleep((int32_t)(sleep_ns / 1000000));
#else
struct timespec req;
req.tv_nsec = sleep_ns % UINT64_C(1000000000);
req.tv_sec = (time_t)(sleep_ns / UINT64_C(1000000000));
nanosleep(&req, NULL);
#endif /* #if DX_PLATFORM_WINDOWS */
}
uint64_t time_ns()
{
#if DX_PLATFORM_WINDOWS
union
{
::FILETIME ft;
uint64_t ft64;
} ft;
GetSystemTimeAsFileTime(&ft.ft);
return ft.ft64 * UINT64_C(100);
#elif defined(__APPLE__)
struct timeval system_time;
if (0 != gettimeofday(&system_time, NULL))
return 0;
return system_time.tv_sec * UINT64_C(1000000000) + system_time.tv_usec * UINT64_C(1000);
#else
// Linux, etc.
struct timespec system_time;
clock_gettime(CLOCK_REALTIME, &system_time);
return system_time.tv_sec * UINT64_C(1000000000) + system_time.tv_nsec;
#endif
}
uint64_t time_us()
{
#if DX_PLATFORM_WINDOWS
union
{
FILETIME ft;
uint64_t ft64;
} ft;
GetSystemTimeAsFileTime(&ft.ft);
return ft.ft64 / 10;
#elif defined(__APPLE__)
struct timeval system_time;
if (0 != gettimeofday(&system_time, NULL))
return 0;
return system_time.tv_sec * UINT64_C(1000000) + system_time.tv_usec;
#else
// Linux, etc.
struct timespec system_time;
clock_gettime(CLOCK_REALTIME, &system_time);
return system_time.tv_sec * UINT64_C(1000000) + system_time.tv_nsec / 1000U;
#endif
}