使用Python实现系统时间跳变检测与日志记录

文章目录

概述

之前写过:

本文将使用Python脚本实现类似的功能,该Python脚本主要实现以下功能:

  1. 定时记录时间戳:按照指定的毫秒间隔记录当前系统时间。
  2. 每1000次打印一次时间戳:在正常情况下,每经过1000次记录后,将当前时间戳写入日志文件,减少日志量。
  3. 检测时间跳变
    • 时间回退:如果当前时间戳小于上一个时间戳,标记为时间跳变。
    • 时间跳跃:如果当前时间戳与倒数第二个时间戳之间的实际间隔小于1.5倍的预期间隔,标记为时间跳变。
  4. 记录跳变前后的时间戳 :在检测到时间跳变时,记录跳变前的10个时间戳和跳变后的10个时间戳,并在跳变的时间戳上标记[TIME_JUMP]
  5. 日志轮转:当日志文件大小达到指定阈值(默认100MB)时,自动进行日志轮转,避免日志文件过大。

脚本实现

以下是完整的Python脚本代码,随后将逐步解析其实现细节。

python 复制代码
#!/usr/bin/env python3

import argparse
import time
from datetime import datetime
import os
import sys
from collections import deque

def parse_arguments():
    parser = argparse.ArgumentParser(description='Time Jump Detection Script')
    parser.add_argument('-i', '--interval', type=int, default=20, help='Set the time interval in milliseconds (default: 20)')
    parser.add_argument('-f', '--file', type=str, default='timestamps.txt', help='Set the output filename (default: timestamps.txt)')
    parser.add_argument('-t', '--time', type=int, default=7200, help='Set the run time in seconds (default: 7200)')
    parser.add_argument('--disable_selective_logging', action='store_true', help='Disable selective logging feature')
    return parser.parse_args()

def get_current_time_str():
    return datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')

def get_timestamp_microseconds(time_str):
    try:
        dt = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S.%f')
    except ValueError:
        # Handle cases where microseconds are missing
        dt = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
    epoch = datetime(1970, 1, 1)
    delta = dt - epoch
    return int(delta.total_seconds() * 1_000_000)

def rotate_log(filename, max_size):
    if os.path.exists(filename):
        size = os.path.getsize(filename)
        if size >= max_size:
            new_filename = f"{filename}.{datetime.now().strftime('%Y%m%d%H%M%S')}"
            try:
                os.rename(filename, new_filename)
                print(f"Rotated log file to {new_filename}")
            except OSError as e:
                print(f"Failed to rotate log file: {e}", file=sys.stderr)

def main():
    args = parse_arguments()
    interval_sec = args.interval / 1000.0
    filename = args.file
    run_time = args.time
    selective_logging = not args.disable_selective_logging
    max_file_size = 100 * 1024 * 1024  # 100MB

    print(f"Time Interval: {args.interval} milliseconds")
    print(f"Output File: {filename}")
    print(f"Run Time: {run_time} seconds")
    print(f"Selective Logging: {'Enabled' if selective_logging else 'Disabled'}")

    # Initialize variables
    last_timestamp_us = 0
    second_last_timestamp_us = 0
    total_timestamps = 0
    in_jump_mode = False
    jump_remaining = 0
    pre_jump_timestamps = deque(maxlen=10)

    # Initialize log file
    try:
        with open(filename, 'w') as f:
            pass  # Just to create or clear the file
    except Exception as e:
        print(f"Failed to initialize log file: {e}", file=sys.stderr)
        sys.exit(1)

    try:
        with open(filename, 'a') as f:
            start_time = time.time()
            while (time.time() - start_time) < run_time:
                current_time_str = get_current_time_str()
                current_timestamp_us = get_timestamp_microseconds(current_time_str)

                time_jump = False
                if last_timestamp_us != 0 and second_last_timestamp_us != 0 and selective_logging:
                    # Check for time regression (time going backwards)
                    if current_timestamp_us < last_timestamp_us:
                        time_jump = True
                    else:
                        # Check if the interval is too large based on second last timestamp
                        expected_interval_us = args.interval * 1000
                        actual_interval_us = current_timestamp_us - second_last_timestamp_us
                        threshold_us = int(expected_interval_us * 1.5)  # 1.5x threshold
                        if actual_interval_us < threshold_us:
                            time_jump = True

                # Update timestamps
                second_last_timestamp_us = last_timestamp_us
                last_timestamp_us = current_timestamp_us

                # Add current timestamp to pre-jump buffer
                pre_jump_timestamps.append(current_time_str)

                if selective_logging and time_jump and not in_jump_mode:
                    # Detected a time jump, enter jump mode
                    in_jump_mode = True
                    jump_remaining = 10  # Number of post-jump timestamps to record

                    # Log pre-jump timestamps
                    f.write("\n--- TIME JUMP DETECTED ---\n")
                    for ts in list(pre_jump_timestamps):
                        f.write(f"{ts}\n")

                    # Log current (jump) timestamp with [TIME_JUMP]
                    f.write(f"{current_time_str} [TIME_JUMP]\n")
                    f.flush()

                elif in_jump_mode:
                    # In jump mode, record post-jump timestamps
                    f.write(f"{current_time_str}\n")
                    f.flush()
                    jump_remaining -= 1
                    if jump_remaining <= 0:
                        in_jump_mode = False

                else:
                    # Normal logging: every 1000 timestamps
                    total_timestamps += 1
                    if total_timestamps % 1000 == 0:
                        f.write(f"{current_time_str}\n")
                        f.flush()

                # Rotate log if needed
                rotate_log(filename, max_file_size)

                # Sleep for the specified interval
                time.sleep(interval_sec)
    except KeyboardInterrupt:
        print("Program interrupted by user.")
    except Exception as e:
        print(f"An error occurred: {e}", file=sys.stderr)

    print("Program has ended.")

if __name__ == "__main__":
    main()

参数解析

脚本通过命令行参数进行配置,支持以下选项:

  • -i--interval:设置时间记录的间隔,单位为毫秒,默认值为20毫秒。
  • -f--file:设置输出日志文件的名称,默认值为timestamps.txt
  • -t--time:设置脚本运行的总时长,单位为秒,默认值为7200秒(2小时)。
  • --disable_selective_logging:禁用选择性记录功能,即不记录时间跳变前后的时间戳。

通过argparse模块,脚本能够灵活地接受和解析这些参数,使其更具通用性和可配置性。

时间戳获取与转换

脚本通过以下两个函数获取并转换当前时间:

  • get_current_time_str():获取当前系统时间,格式为YYYY-MM-DD HH:MM:SS.UUUUUU,精确到微秒。
  • get_timestamp_microseconds(time_str):将时间字符串转换为自1970年1月1日以来的微秒数,用于后续的时间跳变检测。

这种转换方式确保了时间戳的精确性和可比较性,为时间跳变检测提供了基础。

日志轮转

为了防止日志文件过大,脚本实现了日志轮转功能:

python 复制代码
def rotate_log(filename, max_size):
    if os.path.exists(filename):
        size = os.path.getsize(filename)
        if size >= max_size:
            new_filename = f"{filename}.{datetime.now().strftime('%Y%m%d%H%M%S')}"
            try:
                os.rename(filename, new_filename)
                print(f"Rotated log file to {new_filename}")
            except OSError as e:
                print(f"Failed to rotate log file: {e}", file=sys.stderr)

当日志文件大小达到或超过max_size(默认100MB)时,脚本会将当前日志文件重命名为包含时间戳的文件名,并创建一个新的日志文件继续记录。这种机制确保了日志文件的可管理性和系统的稳定运行。

时间跳变检测逻辑

时间跳变检测是脚本的核心功能,主要通过以下逻辑实现:

  1. 时间回退检测:如果当前时间戳小于上一个时间戳,表示系统时间回退,标记为时间跳变。
  2. 时间跳跃检测:如果当前时间戳与倒数第二个时间戳之间的实际间隔小于1.5倍的预期间隔,标记为时间跳变。这种情况可能由于系统时间被大幅调整或其他异常导致。

检测到时间跳变后,脚本会记录跳变前的10个时间戳、跳变时的时间戳(标记为[TIME_JUMP]),以及跳变后的10个时间戳,以便后续分析和诊断。

主循环

脚本的主循环负责持续记录时间戳、检测时间跳变以及执行日志轮转。以下是主要步骤:

  1. 获取当前时间戳 :通过get_current_time_str()get_timestamp_microseconds()获取并转换当前时间。
  2. 时间跳变检测:根据上述逻辑判断是否发生时间跳变。
  3. 记录时间戳
    • 正常情况:每1000次记录一次时间戳,减少日志量。
    • 检测到跳变:记录跳变前后的时间戳,并标记跳变事件。
  4. 日志轮转:检查日志文件大小,必要时执行轮转。
  5. 暂停:按照设定的时间间隔暂停,控制记录频率。

使用方法

运行脚本

使用默认参数运行脚本:

bash 复制代码
./time_jump_check.py
自定义参数
  • 设置时间间隔为10毫秒,运行时间为1小时,输出文件为output.txt

    bash 复制代码
    ./time_jump_check.py -i 10 -t 3600 -f output.txt
  • 禁用选择性记录功能

    bash 复制代码
    ./time_jump_check.py --disable_selective_logging

手动修改系统时间以测试时间跳变检测

在另一个终端中,使用以下命令手动修改系统时间:

bash 复制代码
sudo date -s "2024-10-09 12:30:00"

注意:手动修改系统时间需要管理员权限,并且会影响系统上的其他程序,请谨慎操作。

检查日志文件

查看日志文件(默认timestamps.txt或自定义名称):

bash 复制代码
cat timestamps.txt

应包含时间戳记录,并在时间跳变时标记[TIME_JUMP]。例如:

2024-10-09 13:10:49.672701
...
--- TIME JUMP DETECTED ---
2024-10-09 12:30:00.002615 [TIME_JUMP]
2024-10-09 12:30:00.011569
2024-10-09 12:30:00.013045
...
相关推荐
人人人人一样一样1 分钟前
作业Python
python
四口鲸鱼爱吃盐20 分钟前
Pytorch | 利用VMI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
四口鲸鱼爱吃盐28 分钟前
Pytorch | 利用PI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
小陈phd1 小时前
深度学习之超分辨率算法——SRCNN
python·深度学习·tensorflow·卷积
CodeClimb1 小时前
【华为OD-E卷-简单的自动曝光 100分(python、java、c++、js、c)】
java·python·华为od
数据小小爬虫1 小时前
如何利用Python爬虫获取商品历史价格信息
开发语言·爬虫·python
NiNg_1_2341 小时前
Python的sklearn中的RandomForestRegressor使用详解
开发语言·python·sklearn
黑色叉腰丶大魔王1 小时前
《基于 Python 的网页爬虫详细教程》
开发语言·爬虫·python
laity171 小时前
爬取小说案例-BeautifulSoup教学篇
爬虫·python
WANGWUSAN661 小时前
Python高频写法总结!
java·linux·开发语言·数据库·经验分享·python·编程