core/common/TimeUtil.cpp (357 lines of code) (raw):
// Copyright 2022 iLogtail 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 "TimeUtil.h"
#include <cmath>
#include <memory.h>
#include <atomic>
#include <chrono>
#include <limits>
#if defined(__linux__)
#include <ctime>
#include <sys/sysinfo.h>
#include <utmp.h>
#endif
#include "common/LogtailCommonFlags.h"
#include "common/ParamExtractor.h"
#include "common/StringTools.h"
#include "common/Strptime.h"
#include "logger/Logger.h"
namespace logtail {
const std::string PRECISE_TIMESTAMP_DEFAULT_KEY = "precise_timestamp";
std::string ConvertToTimeStamp(const time_t& t, const std::string& format) {
return GetTimeStamp(t, format);
}
std::string GetTimeStamp(time_t t, const std::string& format) {
if (0 == strcmp(format.c_str(), "%s")) {
return t <= 0 ? "" : std::to_string(t);
}
struct tm timeInfo;
#if defined(__linux__)
if (NULL == localtime_r(&t, &timeInfo))
return "";
#elif defined(_MSC_VER)
if (0 != localtime_s(&timeInfo, &t))
return "";
#endif
char buf[256];
auto ret = strftime(buf, 256, format.c_str(), &timeInfo);
return (0 == ret) ? "" : std::string(buf, ret);
}
uint64_t GetCurrentTimeInMicroSeconds() {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch())
.count();
}
uint64_t GetCurrentTimeInMilliSeconds() {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
.count();
}
int GetLocalTimeZoneOffsetSecond() {
time_t nowTime = time(NULL);
tm timeInfo;
memset(&timeInfo, 0, sizeof(timeInfo));
#if defined(__linux__)
localtime_r(&nowTime, &timeInfo);
return timeInfo.tm_gmtoff;
#else
localtime_s(&timeInfo, &nowTime);
char timezone[50];
auto len = strftime(timezone, sizeof(timezone), "%z", &timeInfo); // +0800
if (len <= 1)
return 0;
int sign = ('+' == timezone[0]) ? 1 : -1;
int num = atoi(&timezone[1]);
return sign * ((num % 100 == 0) ? (num / 100) * 3600 : num * 60);
#endif
}
// DeduceYear decuces year for @tm according to current date (@currentTm).
// @return -1 means that it can not give a deduction.
int DeduceYear(const struct tm* tm, const struct tm* currentTm) {
// Deduction rules.
// 1. Input date is Jan 1, current date is Dec 31, increase year.
if ((0 == tm->tm_mon && 1 == tm->tm_mday) && (11 == currentTm->tm_mon && 31 == currentTm->tm_mday)) {
return currentTm->tm_year + 1;
}
// 2. Input date is Dec 31, current date is Jan 1, descrease year.
if ((11 == tm->tm_mon && 31 == tm->tm_mday) && (0 == currentTm->tm_mon && 1 == currentTm->tm_mday)) {
return currentTm->tm_year - 1;
}
// 3. Assign directly.
return currentTm->tm_year;
}
/*
Parse time (local timezone) from log
return the position of the parsing ends. If parsing fails, return NULL.
*/
const char*
Strptime(const char* buf, const char* fmt, LogtailTime* ts, int& nanosecondLength, int32_t specifiedYear /* = -1 */) {
struct tm tm_ = {0};
struct tm* tm = &tm_;
const int32_t MIN_YEAR = std::numeric_limits<decltype(tm->tm_year)>::min();
auto bakYear = tm->tm_year;
tm->tm_year = MIN_YEAR;
auto ret = strptime_ns(buf, fmt, tm, &ts->tv_nsec, &nanosecondLength);
if (0 == strcmp("%f", fmt)) {
return ret;
}
if (specifiedYear < 0) {
ts->tv_sec = mktime(tm);
return ret;
}
// Do not specify: already got year information.
if (tm->tm_year != MIN_YEAR) {
ts->tv_sec = mktime(tm);
return ret;
}
// Mode 1.
if (specifiedYear > 0) {
tm->tm_year = specifiedYear - 1900;
ts->tv_sec = mktime(tm);
return ret;
}
// Mode 2: deduce year according to current time.
tm->tm_year = bakYear;
struct tm currentTm = {0};
time_t currentTime = time(0);
#if defined(_MSC_VER)
if (localtime_s(¤tTm, ¤tTime) != 0)
#elif defined(__linux__)
if (NULL == localtime_r(¤tTime, ¤tTm))
#endif
{
LOG_WARNING(sLogger, ("Call localtime failed, errno", errno));
return ret;
}
auto deduction = DeduceYear(tm, ¤tTm);
if (deduction != -1)
tm->tm_year = deduction;
ts->tv_sec = mktime(tm);
return ret;
}
#if defined(__linux__)
int ReadUtmp(const char* filename, int* n_entries, utmp** utmp_buf) {
FILE* utmp_file;
struct stat file_stats;
size_t n_read;
size_t size;
struct utmp* buf;
utmp_file = fopen(filename, "r");
if (utmp_file == NULL)
return 1;
if (fstat(fileno(utmp_file), &file_stats) != 0) {
fclose(utmp_file);
return 1;
}
size = file_stats.st_size;
if (size > 0)
buf = (utmp*)malloc(size);
else {
fclose(utmp_file);
return 1;
}
/* Use < instead of != in case the utmp just grew. */
n_read = fread(buf, 1, size, utmp_file);
if (ferror(utmp_file) || fclose(utmp_file) == EOF || n_read < size) {
free(buf);
return 1;
}
*n_entries = size / sizeof(utmp);
*utmp_buf = buf;
return 0;
}
int32_t GetBootTimeFromUtmp() {
int32_t bootTime = -1;
int n_users = 0;
utmp* utmp_buf = NULL;
do {
int fail = ReadUtmp("/var/run/utmp", &n_users, &utmp_buf);
if (fail) {
APSARA_LOG_ERROR(sLogger, ("failed to read utmp file", ""));
break;
}
utmp* head = utmp_buf;
while (n_users-- > 0) {
if (head->ut_type == RUN_LVL) {
bootTime = head->ut_tv.tv_sec;
break;
}
++head;
}
} while (0);
if (utmp_buf != NULL)
free(utmp_buf);
return bootTime;
}
int32_t GetUpTime() {
time_t uptime = 0;
FILE* fp = fopen("/proc/uptime", "r");
if (fp != NULL) {
const int sz = 8192;
char buf[sz];
double upsecs;
int res;
char* b = fgets(buf, sz, fp);
if (b == buf) {
/* The following sscanf must use the C locale. */
setlocale(LC_NUMERIC, "C");
res = sscanf(buf, "%lf", &upsecs);
setlocale(LC_NUMERIC, "");
if (res == 1) {
uptime = (time_t)upsecs;
} else {
APSARA_LOG_ERROR(sLogger, ("failed to /proc/uptime", ""));
}
}
fclose(fp);
}
return uptime;
}
#endif
int32_t GetSystemBootTime() {
#if defined(__linux__)
int32_t bootTime = 0;
int32_t upTime = GetUpTime();
if (upTime > 0) {
time_t currentTime = time(NULL);
bootTime = currentTime - upTime;
APSARA_LOG_INFO(sLogger, ("get system boot time from /proc/uptime", bootTime));
return bootTime;
}
int32_t utmpBootTime = GetBootTimeFromUtmp();
if (utmpBootTime > 0) {
APSARA_LOG_INFO(sLogger, ("get system boot time from /var/run/utmp", utmpBootTime));
return utmpBootTime;
}
return 0; // unexpected
#elif defined(_MSC_VER)
// TODO:
return -1;
#endif
}
static std::atomic<int32_t> sTimeDelta{0};
time_t GetTimeDelta() {
return sTimeDelta;
}
void UpdateTimeDelta(time_t serverTime) {
sTimeDelta = serverTime - time(0);
APSARA_LOG_INFO(sLogger, ("update time delta by server time", serverTime)("delta", sTimeDelta));
}
// DEPRECATED: only for the compability of PreciseTimestamp
uint64_t GetPreciseTimestampFromLogtailTime(LogtailTime logTime, const PreciseTimestampConfig& preciseTimestampConfig) {
uint64_t preciseTimestamp = logTime.tv_sec;
TimeStampUnit timeUnit = preciseTimestampConfig.unit;
if (TimeStampUnit::MILLISECOND == timeUnit) {
return preciseTimestamp * 1000 + logTime.tv_nsec / 1000000;
} else if (TimeStampUnit::MICROSECOND == timeUnit) {
return preciseTimestamp * 1000000 + logTime.tv_nsec / 1000;
} else if (TimeStampUnit::NANOSECOND == timeUnit) {
return preciseTimestamp * 1000000000 + logTime.tv_nsec;
}
return preciseTimestamp;
}
void SetLogTime(sls_logs::Log* log, time_t second) {
log->set_time(second);
}
LogtailTime GetCurrentLogtailTime() {
LogtailTime ts;
auto now = std::chrono::system_clock::now().time_since_epoch();
ts.tv_sec = std::chrono::duration_cast<std::chrono::seconds>(now).count();
ts.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count() % 1000000000;
return ts;
}
// Parse ms/us/ns suffix from preciseTimeSuffix, joining with the input second timestamp.
// Will return value the precise timestamp.
uint64_t GetPreciseTimestamp(uint64_t secondTimestamp,
const char* preciseTimeSuffix,
const PreciseTimestampConfig& preciseTimestampConfig) {
bool endFlag = false;
TimeStampUnit timeUnit = preciseTimestampConfig.unit;
uint32_t maxPreciseDigitNum = 0;
if (TimeStampUnit::MILLISECOND == timeUnit) {
maxPreciseDigitNum = 3;
} else if (TimeStampUnit::MICROSECOND == timeUnit) {
maxPreciseDigitNum = 6;
} else if (TimeStampUnit::NANOSECOND == timeUnit) {
maxPreciseDigitNum = 9;
} else {
maxPreciseDigitNum = 0;
}
if (NULL == preciseTimeSuffix || strlen(preciseTimeSuffix) <= 1) {
endFlag = true;
} else {
static std::string supportedSeparators = ".,: ";
const char separator = preciseTimeSuffix[0];
std::size_t found = supportedSeparators.find(separator);
if (found == std::string::npos) {
endFlag = true;
}
}
uint32_t preciseTimeDigit = 0;
for (uint32_t i = 0; i < maxPreciseDigitNum; i++) {
bool validDigit = false;
if (!endFlag) {
const char digitChar = preciseTimeSuffix[i + 1];
if (digitChar != '\0' && digitChar >= '0' && digitChar <= '9') {
preciseTimeDigit = preciseTimeDigit * 10 + (digitChar - '0');
validDigit = true;
}
}
if (!validDigit) {
// Insufficient digits filled with zeros.
preciseTimeDigit = preciseTimeDigit * 10;
endFlag = true;
}
secondTimestamp *= 10;
}
return secondTimestamp + preciseTimeDigit;
}
uint64_t GetCurrentTimeInNanoSeconds() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch())
.count();
}
bool ParseTimeZoneOffsetSecond(const std::string& logTZ, int& logTZSecond) {
if (logTZ.size() != strlen("GMT+08:00") || logTZ[6] != ':' || (logTZ[3] != '+' && logTZ[3] != '-')) {
return false;
}
if (logTZ.find("GMT") != (size_t)0) {
return false;
}
std::string hourStr = logTZ.substr(4, 2);
std::string minuteStr = logTZ.substr(7, 2);
int hour{};
int minute{};
if (!StringTo(hourStr, hour) || !StringTo(minuteStr, minute)) {
return false;
}
logTZSecond = hour * 3600 + minute * 60;
if (logTZ[3] == '-') {
logTZSecond = -logTZSecond;
}
return true;
}
bool ParseLogTimeZoneOffsetSecond(const std::string& logTZ, int& logTimeZoneOffsetSecond) {
if (logTZ.empty()) {
return true;
}
int logTZSecond = 0;
if (!ParseTimeZoneOffsetSecond(logTZ, logTZSecond)) {
return false;
}
logTimeZoneOffsetSecond = logTZSecond - GetLocalTimeZoneOffsetSecond();
return true;
}
std::string NumberToDigitString(uint32_t number, uint8_t length) {
std::ostringstream oss;
oss << std::setw(length) << std::setfill('0') << number;
std::string result = oss.str();
if (result.length() > length) {
result = result.substr(result.length() - length, length);
}
return result;
}
long GetTicksPerSecond() {
static long sTicksPerSecond = sysconf(_SC_CLK_TCK);
return sTicksPerSecond;
}
std::chrono::nanoseconds GetTimeDiffFromMonotonic() {
#if defined(__linux__)
struct timespec t;
int ret = clock_gettime(CLOCK_MONOTONIC, &t);
if (ret != 0) {
LOG_ERROR(sLogger, ("failed to get monotonic, ret", ret));
return std::chrono::nanoseconds(0);
}
auto now = std::chrono::system_clock::now();
auto now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
auto boot_ns = t.tv_sec * 1000000000ULL + t.tv_nsec;
return std::chrono::nanoseconds(now_ns - boot_ns);
#else
return std::chrono::nanoseconds(0);
#endif
}
struct timespec ConvertKernelTimeToUnixTime(uint64_t nano) {
static auto diff = GetTimeDiffFromMonotonic().count();
auto ts = std::chrono::nanoseconds(nano + diff);
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ts);
auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(ts - seconds);
struct timespec res = {seconds.count(), nanoseconds.count()};
return res;
}
} // namespace logtail