文章目录
0. 概述
本文档描述了一个用C++实现的高精度时间日志记录程序,该程序旨在检查系统时间是否出现跳变。程序可以在指定的时间间隔内(默认20毫秒)将系统时间记录到日志文件中,并具备以下主要功能:
- 自定义时间间隔、文件名和运行时间:通过命令行参数设置时间间隔、输出文件名和总运行时间。默认值为20毫秒、
timestamps_cpp.txt
和72000秒(20小时)。 - 高精度时间戳记录:使用
gettimeofday
获取系统时间,确保时间戳精确到微秒级。 - 日志轮转:当日志文件大小超过50MB时,自动进行日志轮转,将当前日志文件重命名为旧文件并生成新的日志文件。
- 时间跳变检测:在日志中标记时间跳变,检测逻辑包括:
- 时间回退,即当前时间戳小于前一个时间戳。
- 间隔异常,即实际间隔小于设定间隔的1.5倍*(实际应该是2倍,这里写1.5倍允许适当的误差)*。
在默认设置下,程序仅记录跳变前后10个时间戳,以简化日志内容,突出异常信息。
1. 代码实现
1.1 概览
该程序按如下步骤实现:
- 参数解析:从命令行解析时间间隔、文件名、运行时间和是否禁用跳变检测。
- 时间记录与跳变检测:记录系统时间,检测时间跳变,进行相应日志输出。
- 日志轮转:根据文件大小自动轮转日志文件。
流程图
graph TD
A[开始] --> B[解析命令行参数]
B --> C[初始化日志文件]
C --> D[记录系统时间]
D --> E[检测时间跳变]
E -->|是| F[进入跳变模式,记录跳变前后时间戳]
E -->|否| G[普通时间戳记录]
F --> H[轮转日志文件(若需要)]
G --> H
H --> I[等待指定时间间隔]
I -->|未到达运行时间| D
I -->|到达运行时间| J[程序结束]
1.2 获取当前时间字符串
使用gettimeofday
和strftime
将当前时间格式化为字符串,精确到微秒。
std::string getCurrentTimeStr() {
struct timeval tv;
gettimeofday(&tv, nullptr);
struct tm tmInfo;
localtime_r(&tv.tv_sec, &tmInfo);
char buffer[30];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tmInfo);
// Append microseconds
std::ostringstream oss;
oss << buffer << "." << std::setw(6) << std::setfill('0') << tv.tv_usec;
return oss.str();
}
1.3 文件大小检查与日志轮转
使用stat
函数获取日志文件大小,并在超过50MB时进行轮转。轮转方式是将当前日志文件重命名为.old
并创建新的日志文件。
void rotateLog(const std::string &filename, size_t maxSize, FILE **logFilePtr) {
struct stat statBuf;
if (stat(filename.c_str(), &statBuf) == 0) {
if (static_cast<size_t>(statBuf.st_size) >= maxSize) {
fclose(*logFilePtr);
std::string oldFilename = filename + ".old";
rename(filename.c_str(), oldFilename.c_str());
*logFilePtr = fopen(filename.c_str(), "a");
}
}
}
1.4 时间跳变检测
程序使用时间戳微秒数进行跳变检测,包括两种跳变情况:
- 时间回退:当前时间小于前一个时间。
- 间隔异常:当前时间与上上个时间间隔过大。
当跳变检测到时,进入“跳变模式”,记录前后共20个时间戳,并在跳变处添加 [TIME_JUMP]
标记。
2. 编译与运行
2.1 编译
在终端中使用如下命令编译:
g++ -std=c++11 -o time_logger time_logger.cpp
2.2 运行
- 使用默认设置(20毫秒间隔,输出文件为
timestamps_cpp.txt
,运行20小时):
./time_logger
- 自定义设置(例如,100毫秒间隔,输出文件为
output.txt
,运行2小时):
./time_logger -i 100 -f output.txt -t 7200
2.3 示例日志文件
日志文件示例:
2024-04-27 12:34:56.789123
2024-04-27 12:34:56.829456
2024-04-27 12:34:56.869789
2024-04-27 12:34:56.809012 [TIME_JUMP]
2024-04-27 12:34:56.849345
...
3. 附完整代码
// g++ -std=c++11 -pthread -o time_logger time_logger.cpp
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
// Struct to store command-line arguments
struct Arguments {
int interval = 20; // milliseconds
std::string file = "timestamps_cpp.txt";
int time = 72000; // seconds
bool disableSelectiveLogging = false;
};
// Function to parse command-line arguments
Arguments parseArguments(int argc, char *argv[]) {
Arguments args;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if ((arg == "-i" || arg == "--interval") && i + 1 < argc) {
args.interval = std::stoi(argv[++i]);
} else if ((arg == "-f" || arg == "--file") && i + 1 < argc) {
args.file = argv[++i];
} else if ((arg == "-t" || arg == "--time") && i + 1 < argc) {
args.time = std::stoi(argv[++i]);
} else if (arg == "--disable_selective_logging") {
args.disableSelectiveLogging = true;
} else if (arg == "-h" || arg == "--help") {
fprintf(stdout, "Usage: %s [options]\n", argv[0]);
fprintf(stdout, "Options:\n");
fprintf(stdout, " -i, --interval <milliseconds> Set the time "
"interval (default: 20 milliseconds)\n");
fprintf(stdout, " -f, --file <filename> Set the output "
"filename (default: timestamps.txt)\n");
fprintf(stdout, " -t, --time <seconds> Set the run time "
"(default: 72000 seconds)\n");
fprintf(stdout,
" --disable_selective_logging Disable selective logging\n");
fprintf(stdout,
" -h, --help Show this help message\n");
exit(0);
} else {
fprintf(stderr, "Unknown argument: %s\n", arg.c_str());
fprintf(stderr, "Use -h or --help to see available options.\n");
exit(EXIT_FAILURE);
}
}
return args;
}
// Function to get the current time as a formatted string
std::string getCurrentTimeStr() {
struct timeval tv;
gettimeofday(&tv, nullptr);
struct tm tmInfo;
localtime_r(&tv.tv_sec, &tmInfo);
char buffer[30];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tmInfo);
// Append microseconds
std::ostringstream oss;
oss << buffer << "." << std::setw(6) << std::setfill('0') << tv.tv_usec;
return oss.str();
}
// Function to get the timestamp in microseconds since epoch
uint64_t getTimestampMicroseconds() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
}
// Function to rotate the log file, keeping only two log files (current and
// old), based on file size
void rotateLog(const std::string &filename, size_t maxSize, FILE **logFilePtr) {
struct stat statBuf;
// Check if the file exists and retrieve its size
if (stat(filename.c_str(), &statBuf) == 0) {
// If the file size exceeds the maximum allowed size
if (static_cast<size_t>(statBuf.st_size) >= maxSize) {
// Close the current log file
fclose(*logFilePtr);
// Rename the current log file to "filename.old"
std::string oldFilename = filename + ".old";
if (rename(filename.c_str(), oldFilename.c_str()) != 0) {
fprintf(stderr, "Failed to rotate log file: %s\n", strerror(errno));
// Attempt to reopen the original log file even if renaming failed
*logFilePtr = fopen(filename.c_str(), "a");
if (!*logFilePtr) {
fprintf(stderr,
"Failed to reopen log file after failed rotation: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
return;
}
// Open a new log file for writing
*logFilePtr = fopen(filename.c_str(), "a");
if (!*logFilePtr) {
fprintf(stderr, "Failed to open new log file after rotation: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
fprintf(stdout, "Rotated log file: %s -> %s\n", filename.c_str(),
oldFilename.c_str());
}
}
}
int main(int argc, char *argv[]) {
Arguments args = parseArguments(argc, argv);
double intervalSec = args.interval / 1000.0;
std::string filename = args.file;
int runTime = args.time;
bool selectiveLogging = !args.disableSelectiveLogging;
const size_t maxFileSize = 50 * 1024 * 1024; // 50MB
fprintf(stdout, "Time Interval: %d milliseconds\n", args.interval);
fprintf(stdout, "Output File: %s\n", filename.c_str());
fprintf(stdout, "Run Time: %d seconds\n", runTime);
fprintf(stdout, "Selective Logging: %s\n",
(selectiveLogging ? "Enabled" : "Disabled"));
// Initialize variables
uint64_t lastTimestampUs = 0;
uint64_t secondLastTimestampUs = 0;
uint64_t totalTimestamps = 0;
bool inJumpMode = false;
int jumpRemaining = 0;
std::deque<std::string> preJumpTimestamps;
const size_t preJumpBufferSize = 10;
// Open log file using fprintf
FILE *logFile = fopen(filename.c_str(), "w");
if (!logFile) {
fprintf(stderr, "Failed to initialize log file.\n");
return EXIT_FAILURE;
}
// Get start time
struct timeval startTimeVal;
gettimeofday(&startTimeVal, nullptr);
time_t startTimeSec = startTimeVal.tv_sec;
uint32_t timeJumpCount = 0;
while (true) {
// Check elapsed time
struct timeval currentTimeVal;
gettimeofday(¤tTimeVal, nullptr);
double elapsedTime =
currentTimeVal.tv_sec - startTimeSec + currentTimeVal.tv_usec / 1e6;
if ((elapsedTime >= runTime) && (timeJumpCount > runTime)) {
// if not jump too much, timeJumpCount should large than runTime
break;
} else {
timeJumpCount = timeJumpCount + 1;
}
// Get current time string and timestamp
std::string currentTimeStr = getCurrentTimeStr();
uint64_t currentTimestampUs = getTimestampMicroseconds();
bool timeJump = false;
if (lastTimestampUs != 0 && secondLastTimestampUs != 0 &&
selectiveLogging) {
if (currentTimestampUs < lastTimestampUs) {
// Time regression detected
timeJump = true;
} else {
// Check if the interval is too large
uint64_t expectedIntervalUs = args.interval * 1000;
uint64_t actualIntervalUs = currentTimestampUs - secondLastTimestampUs;
uint64_t thresholdUs = static_cast<uint64_t>(expectedIntervalUs * 1.5);
if (actualIntervalUs < thresholdUs) {
timeJump = true;
}
}
}
// Update timestamps
secondLastTimestampUs = lastTimestampUs;
lastTimestampUs = currentTimestampUs;
// Add to pre-jump buffer
if (preJumpTimestamps.size() >= preJumpBufferSize) {
preJumpTimestamps.pop_front();
}
preJumpTimestamps.push_back(currentTimeStr);
if (selectiveLogging && timeJump && !inJumpMode) {
// Time jump detected, enter jump mode
inJumpMode = true;
jumpRemaining = 10; // Record the next 10 timestamps
// Log pre-jump timestamps
fprintf(logFile, "\n--- TIME JUMP DETECTED ---\n");
for (const auto &ts : preJumpTimestamps) {
fprintf(logFile, "%s\n", ts.c_str());
}
// Log current (jump) timestamp with [TIME_JUMP]
fprintf(logFile, "%s [TIME_JUMP]\n", currentTimeStr.c_str());
fflush(logFile);
} else if (inJumpMode) {
// In jump mode, record post-jump timestamps
fprintf(logFile, "%s\n", currentTimeStr.c_str());
fflush(logFile);
jumpRemaining--;
if (jumpRemaining <= 0) {
inJumpMode = false;
}
} else {
// Normal logging: every 1000 timestamps
totalTimestamps++;
if (totalTimestamps % 1000 == 0) {
fprintf(logFile, "%s\n", currentTimeStr.c_str());
fflush(logFile);
}
}
// Rotate log file if needed
rotateLog(filename, maxFileSize, &logFile);
// Sleep for the specified interval
usleep(static_cast<useconds_t>(intervalSec * 1e6));
}
// Close the log file
fclose(logFile);
fprintf(stdout, "Program has ended.\n");
return 0;
}