使用shell实现高精度时间日志记录与时间跳变检测

news2024/12/21 21:54:12

文章目录

    • 0. 概述
    • 1. 使用说明
    • 1.1. 参数说明
      • 1.2. 运行脚本
    • 2. 脚本详细解析
      • 2.1. 参数初始化
      • 2.2. 参数解析与验证
      • 2.3 主循环条件
      • 2.4 时间跳变检测与处理
      • 2.5. 日志轮转机制
      • 2.6. 睡眠时间计算

0. 概述

之前写过单线程版本的高精度时间日志记录小程序:C++编程:实现简单的高精度时间日志记录小程序(单线程)
然后写了多线程版本的高精度时间日志记录小程序:使用C++实现高精度时间日志记录与时间跳变检测[多线程版本]

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

  1. 定时记录时间戳:按照指定的时间间隔(默认为20毫秒)记录当前时间戳。
  2. 时间跳变检测与处理:当检测到系统时间回退或跳跃时,记录跳变前后的时间戳,以便后续分析。
  3. 日志轮转:当日志文件达到指定大小(默认为50MB)时,自动轮转日志文件,并保留一定数量的历史日志文件。
  4. 可配置参数:支持通过命令行参数自定义时间间隔、日志文件名、运行时长等配置。

1. 使用说明

1.1. 参数说明

脚本提供了多种可配置参数,用户可以根据需求进行调整:

  • -i, --interval <milliseconds>:设置记录时间戳的时间间隔,默认为20毫秒。
  • -f, --file <filename>:设置输出日志文件的名称,默认为timestamps_sh.txt
  • -t, --time <seconds>:设置脚本的运行时长,默认为72000秒(20小时)。
  • --disable_selective_logging:禁用选择性日志记录功能。
  • -h, --help:显示帮助信息。

1.2. 运行脚本

使用默认参数运行脚本:

./time_jump_check.sh

使用自定义参数运行脚本,例如设置间隔为500毫秒,运行时长为3600秒(1小时):

./time_jump_check.sh -i 500 -t 3600

禁用选择性日志记录功能:

./time_jump_check.sh --disable_selective_logging

2. 脚本详细解析

以下是time_jump_check.sh 脚本的完整代码:

#!/bin/bash

# Default parameters
INTERVAL_MS=20               # Default interval 20 milliseconds
FILENAME="timestamps_sh.txt" # Default log file name
RUN_TIME_SECONDS=72000       # Default run time 72000 seconds (20 hours)
MAX_FILE_SIZE=50*1024*1024   # 50MB in bytes
SELECTIVE_LOGGING=true      # Default to enable selective logging
PRE_JUMP_RECORD=10          # Number of timestamps to record before jump
POST_JUMP_RECORD=10         # Number of timestamps to record after jump
MAX_LOG_FILES=2             # Maximum number of log files to keep

# Function to show usage information
usage() {
    echo "Usage: $0 [options]"
    echo "Options:"
    echo "  -i, --interval <milliseconds>        Set the time interval, default is 20 milliseconds"
    echo "  -f, --file <filename>                Set the output file name, default is timestamps.txt"
    echo "  -t, --time <seconds>                 Set the run time, default is 72000 seconds (20 hours)"
    echo "      --disable_selective_logging      Disable selective logging feature"
    echo "  -h, --help                          Show this help message"
    exit 1
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    key="$1"
    case $key in
        -i|--interval)
        INTERVAL_MS="$2"
        shift # past argument
        shift # past value
        ;;
        -f|--file)
        FILENAME="$2"
        shift
        shift
        ;;
        -t|--time)
        RUN_TIME_SECONDS="$2"
        shift
        shift
        ;;
        --disable_selective_logging)
        SELECTIVE_LOGGING=false
        shift
        ;;
        -h|--help)
        usage
        ;;
        *)
        echo "Unknown option: $1"
        usage
        ;;
    esac
done

# Validate parameters
if ! [[ "$INTERVAL_MS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_MS" -le 0 ]; then
    echo "Error: Interval must be a positive integer."
    usage
fi

if ! [[ "$RUN_TIME_SECONDS" =~ ^[0-9]+$ ]] || [ "$RUN_TIME_SECONDS" -le 0 ]; then
    echo "Error: Run time must be a non-negative integer."
    usage
fi

# Output configuration
echo "Time Interval: $INTERVAL_MS milliseconds"
echo "Output File: $FILENAME"
echo "Run Time: $RUN_TIME_SECONDS seconds"
echo "Selective Logging: $SELECTIVE_LOGGING"

# Initialize variables
last_timestamp_us=0
second_last_timestamp_us=0
total_timestamps=0
in_jump_mode=false
jump_remaining=0
time_jump_count=0

declare -a pre_jump_timestamps=()

# Create or clear the log file
if ! > "$FILENAME"; then
    echo "Error: Unable to create or clear log file '$FILENAME'."
    exit 1
fi

# Main loop start time
START_TIME=$(date +%s)
while [[ $(($(date +%s) - START_TIME)) -lt $RUN_TIME_SECONDS || $time_jump_count -lt $RUN_TIME_SECONDS ]]; do
    # Get the current time string
    current_time_str=$(date +"%Y-%m-%d %H:%M:%S.%6N")

    # Convert current time to microseconds (since epoch)
    current_timestamp_us=$(date +"%s%6N")

    time_jump=false

    if [[ $last_timestamp_us -ne 0 && $second_last_timestamp_us -ne 0 && $SELECTIVE_LOGGING == true ]]; then
        if [[ $current_timestamp_us -lt $last_timestamp_us ]]; then
            # Time regression detected
            time_jump=true
        else
            # Check if the interval is too small based on second last timestamp
            expected_interval_us=$(( INTERVAL_MS * 1000 ))
            actual_interval_us=$(( current_timestamp_us - second_last_timestamp_us ))
            threshold_us=$(( expected_interval_us * 15 / 10 ))  # 1.5x threshold
            if [[ $actual_interval_us -lt $threshold_us ]]; then
                time_jump=true
            fi
        fi
    fi

    # Update timestamps
    second_last_timestamp_us=$last_timestamp_us
    last_timestamp_us=$current_timestamp_us

    # Add current time to pre_jump_timestamps array
    pre_jump_timestamps+=("$current_time_str")
    # Keep array length at most PRE_JUMP_RECORD
    if [[ ${#pre_jump_timestamps[@]} -gt $PRE_JUMP_RECORD ]]; then
        pre_jump_timestamps=("${pre_jump_timestamps[@]: -$PRE_JUMP_RECORD}")
    fi

    if [[ $SELECTIVE_LOGGING == true && $time_jump == true && $in_jump_mode == false ]]; then
        # Detected a time jump, enter jump mode
        in_jump_mode=true
        jump_remaining=$POST_JUMP_RECORD

        # Log pre-jump timestamps
        echo -e "\n--- TIME JUMP DETECTED ---" >> "$FILENAME"
        for ts in "${pre_jump_timestamps[@]}"; do
            echo "$ts" >> "$FILENAME"
        done

        # Log current timestamp with [TIME_JUMP] marker
        echo "$current_time_str [TIME_JUMP]" >> "$FILENAME"

    elif [[ $in_jump_mode == true ]]; then
        # In jump mode, record post-jump timestamps
        echo "$current_time_str" >> "$FILENAME"
        jump_remaining=$((jump_remaining - 1))
        if [[ $jump_remaining -le 0 ]]; then
            in_jump_mode=false
        fi
    else
        # Normal mode: log every 500 timestamps
        total_timestamps=$((total_timestamps + 1))
        if [[ $((total_timestamps % 500)) -eq 0 ]]; then
            echo "$current_time_str" >> "$FILENAME"
        fi
    fi

    # Check and perform log rotation
    current_size=$(stat -c%s "$FILENAME" 2>/dev/null || echo 0)
    if [[ $current_size -ge $MAX_FILE_SIZE ]]; then
        new_filename="${FILENAME}.$(date +"%Y%m%d%H%M%S")"
        if ! mv "$FILENAME" "$new_filename"; then
            echo "Error: Unable to rotate log file to '$new_filename'."
            exit 1
        fi
        echo "Rotated log file to $new_filename"
        # Create a new log file
        if ! > "$FILENAME"; then
            echo "Error: Unable to create new log file '$FILENAME'."
            exit 1
        fi

        # Remove oldest log file if there are more than MAX_LOG_FILES
        log_files=($(ls -t "${FILENAME}".* 2>/dev/null))
        if [[ ${#log_files[@]} -gt $MAX_LOG_FILES ]]; then
            for (( i=$MAX_LOG_FILES; i<${#log_files[@]}; i++ )); do
                if ! rm "${log_files[$i]}"; then
                    echo "Error: Unable to remove old log file '${log_files[$i]}'."
                fi
            done
        fi
    fi

    # Replace awk with bash and printf to calculate sleep_time
    integer_part=$(( INTERVAL_MS / 1000 ))
    fractional_part=$(( INTERVAL_MS % 1000 ))
    # Ensure fractional_part is three digits with leading zeros if necessary
    fractional_part_padded=$(printf "%03d" "$fractional_part")
    sleep_time="${integer_part}.${fractional_part_padded}"

    # Sleep for the specified interval
    sleep "$sleep_time"
done

echo "Program has ended."

exit 0

2.1. 参数初始化

脚本开始部分定义了一系列默认参数,包括时间间隔、日志文件名、运行时长、最大日志文件大小、选择性日志记录开关、记录跳跃前后的时间戳数量以及最大保留的日志文件数量。

INTERVAL_MS=20               # 默认间隔20毫秒
FILENAME="timestamps_sh.txt" # 默认日志文件名
RUN_TIME_SECONDS=72000       # 默认运行时长72000秒(20小时)
MAX_FILE_SIZE=$((50*1024*1024))   # 50MB
SELECTIVE_LOGGING=true      # 默认启用选择性日志记录
PRE_JUMP_RECORD=10          # 跳跃前记录的时间戳数量
POST_JUMP_RECORD=10         # 跳跃后记录的时间戳数量
MAX_LOG_FILES=2             # 最大保留日志文件数量

2.2. 参数解析与验证

通过命令行参数,用户可以自定义脚本的运行参数。脚本使用while循环和case语句解析传入的参数,并进行必要的验证,确保参数的有效性。

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    key="$1"
    case $key in
        -i|--interval)
        INTERVAL_MS="$2"
        shift # 过去参数
        shift # 过去值
        ;;
        -f|--file)
        FILENAME="$2"
        shift
        shift
        ;;
        -t|--time)
        RUN_TIME_SECONDS="$2"
        shift
        shift
        ;;
        --disable_selective_logging)
        SELECTIVE_LOGGING=false
        shift
        ;;
        -h|--help)
        usage
        ;;
        *)
        echo "Unknown option: $1"
        usage
        ;;
    esac
done

# 验证参数
if ! [[ "$INTERVAL_MS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_MS" -le 0 ]; then
    echo "Error: Interval must be a positive integer."
    usage
fi

if ! [[ "$RUN_TIME_SECONDS" =~ ^[0-9]+$ ]] || [ "$RUN_TIME_SECONDS" -le 0 ]; then
    echo "Error: Run time must be a non-negative integer."
    usage
fi

2.3 主循环条件

主循环的条件为:

while [[ $(($(date +%s) - START_TIME)) -lt $RUN_TIME_SECONDS || $time_jump_count -lt $RUN_TIME_SECONDS ]]; do

这意味着,脚本将在以下两个条件之一满足时继续运行:

  1. 已经运行的时间未超过设定的RUN_TIME_SECONDS
  2. 检测到的时间跳变次数未超过RUN_TIME_SECONDS

这种设计确保了即使系统时间发生大幅跳变,脚本仍能继续运行,直到达到预定的运行时长。

2.4 时间跳变检测与处理

脚本通过比较当前时间戳与之前的时间戳来检测时间跳变。当检测到时间回退或时间间隔异常小时,触发跳变处理机制。

if [[ $current_timestamp_us -lt $last_timestamp_us ]]; then
    # 时间回退
    time_jump=true
    time_jump_count=$((time_jump_count + 1))
else
    # 检查时间间隔是否异常
    expected_interval_us=$(( INTERVAL_MS * 1000 ))
    actual_interval_us=$(( current_timestamp_us - second_last_timestamp_us ))
    threshold_us=$(( expected_interval_us * 15 / 10 ))  # 1.5倍阈值
    if [[ $actual_interval_us -lt $threshold_us ]]; then
        time_jump=true
        time_jump_count=$((time_jump_count + 1))
    fi
fi

当检测到时间跳变时,脚本将:

  1. 记录跳变前的时间戳。
  2. 标记当前时间戳为跳变时间。
  3. 在后续的循环中,记录一定数量的跳变后时间戳,确保日志的连续性。

2.5. 日志轮转机制

为防止日志文件过大,脚本实现了日志轮转功能。当日志文件大小超过MAX_FILE_SIZE时,脚本会:

  1. 将当前日志文件重命名为带有时间戳的文件名。
  2. 创建一个新的空日志文件。
  3. 保留最新的MAX_LOG_FILES个日志文件,删除最旧的文件。
# 检查并执行日志轮转
current_size=$(stat -c%s "$FILENAME" 2>/dev/null || echo 0)
if [[ $current_size -ge $MAX_FILE_SIZE ]]; then
    new_filename="${FILENAME}.$(date +"%Y%m%d%H%M%S")"
    if ! mv "$FILENAME" "$new_filename"; then
        echo "Error: Unable to rotate log file to '$new_filename'."
        exit 1
    fi
    echo "Rotated log file to $new_filename"
    # 创建新的日志文件
    if ! > "$FILENAME"; then
        echo "Error: Unable to create new log file '$FILENAME'."
        exit 1
    fi

    # 如果日志文件超过MAX_LOG_FILES个,删除最旧的文件
    log_files=($(ls -t "${FILENAME}".* 2>/dev/null))
    if [[ ${#log_files[@]} -gt $MAX_LOG_FILES ]]; then
        for (( i=$MAX_LOG_FILES; i<${#log_files[@]}; i++ )); do
            if ! rm "${log_files[$i]}"; then
                echo "Error: Unable to remove old log file '${log_files[$i]}'."
            fi
        done
    fi
fi

2.6. 睡眠时间计算

为了实现精确的时间间隔,脚本将INTERVAL_MS分解为整数部分和小数部分,并使用printf确保小数部分为三位数,最后组合成sleep命令所需的格式。

# 计算睡眠时间
integer_part=$(( INTERVAL_MS / 1000 ))
fractional_part=$(( INTERVAL_MS % 1000 ))
# 确保fractional_part为三位数,前面补零
fractional_part_padded=$(printf "%03d" "$fractional_part")
sleep_time="${integer_part}.${fractional_part_padded}"

# 按指定间隔休眠
sleep "$sleep_time"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

25.1 降低采集资源消耗的收益和无用监控指标的判定依据

本节重点介绍 : 降低采集资源消耗的收益哪些是无用指标&#xff0c;什么判定依据 通过 grafana的 mysql 表获取所有的 查询表达式expr通过 获取所有的prometheus rule文件获取所有的 告警表达式expr通过 获取所有的prometheus 采集器接口 获取所有的采集metrics计算可得到现在…

SpringBoot——静态资源访问的四种方式

1.默认的静态资源目录 /static /public /resources /META-INF/resources 动态资源目录&#xff1a;/templates 2.resources静态资源目录图片存放 3. 静态资源访问 3.1.通过路径访问静态资源 http://localhost:8080/a.jpg http://localhost:8080/b.jpg …

《深度学习》OpenCV 物体跟踪 原理及案例解析

目录 一、物体跟踪 1、什么是物体跟踪 2、步骤 1&#xff09;选择跟踪算法 2&#xff09;初始化跟踪器 3&#xff09;在每个视频帧上执行跟踪 4&#xff09;可选的重新初始化 3、原理 二、案例实现 1、完整代码 1&#xff09;使用方式 2&#xff09;运行结果 2、关…

用python做一个简单的画板

一&#xff0c;画板的介绍 画板&#xff08;Paint Board&#xff09;是一个提供用户绘图、涂鸦和创作的平台。现代数字画板通常是由软件程序实现的&#xff0c;具有多种功能。以下是画板的一些主要特征和功能&#xff1a; 1. 基本绘图工具 画笔和铅笔&#xff1a;用户可以选…

Java——数组的定义与使用

各位看官&#xff1a;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 一&#xff1a;数组的概念以及定义,初始化 1.1&#xff1a;数组概念以及定义 数组概念&#xff1a;可以看成…

红黑树:c++实现

1. 红⿊树的概念 红⿊树是⼀棵⼆叉搜索树&#xff0c;他的每个结点增加⼀个存储位来表⽰结点的颜⾊&#xff0c;可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束&#xff0c;红⿊树确保没有⼀条路径会⽐其他路 径⻓出2倍&#xff0c;因⽽是接近平…

用LaTeX写一篇帅帅的算法学习题解

前言 先来看看用 LaTeX \text{LaTeX} LaTeX 写出来的题解是啥样&#xff0c;内容是瞎写的。 前提知识与环境 默认已经掌握了 LaTeX \text{LaTeX} LaTeX 的一些用法&#xff0c;特别是公式如何写&#xff0c;如果你对这块还是很了解&#xff0c;可以先学着使用 Typora \…

DEEP TEMPORAL GRAPH CLUSTERING.md

ICLR23 推荐指数&#xff1a; #paper/⭐ 原因的话&#xff0c;可以找找前人的一篇文章(可以看&#xff0c;但是当你阅读前人文章会发现。) 动机/优点 很明确&#xff0c;时序图只需要考虑时间相近的点&#xff0c;因此开销特别小。但是邻接矩阵&#xff0c;就要考虑所有点的关…

Dockerfile最佳实践:如何创建高效的容器

在微服务和云计算时代&#xff0c;Docker就已经成为应用开发和部署不可或缺的工具。如今虽处大模型时代&#xff0c;但这些基础技术仍然是我们需要掌握的。 容器化允许开发者将应用程序及其依赖打包到一个单一的、可移植的单元中&#xff0c;确保了可预测性、可扩展性和快速部…

反射机制(Reflection)

1. 反射 Java的反射机制(reflection)是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff1b;这种动态获取信息以及动态调用对象方法的功能称为java语言的反…

创建一个c#程序,实现字符串类型转整数类型

首先&#xff0c;创建一个c#程序 在代码编辑器中编写代码&#xff0c;点击Run按钮或者按下F5键来运行程序。 下面&#xff0c;编写将字符串类型转换为整数类型的代码。 sing System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Task…

安装samples/1_Utilities/deviceQuery等文件

本文章摘抄来自https://zhuanlan.zhihu.com/p/666647168 从cuda11.6开始cuda toolkit就不自带cuda-samples了&#xff0c;而deviceQuery又是cuda-sample的一个子库&#xff0c;所以需要自己手动装一下。 我的系统是ubuntu20.04&#xff0c;已经安装了CUDA Toolkit 12.2。 第…

【深度学习】使用FasterRCNN模型训练自己的数据集(记录全流程

此处用的FasterRCNN模型使用的是B导的源码&#xff0c;读者可以去B站搜B导的视频进行了解和学习&#xff0c;视频中B导非常细心讲解了如何训练自己的数据集以及预测。 文章目录 前言一、准备数据集二、环境配置2.1 基础环境2.2 其他依赖包安装2.3 预训练权重下载 二、训练数据集…

程序员成长秘籍:是迈向管理巅峰,还是深耕技术架构?

专业在线打字练习平台-巧手打字通&#xff0c;只输出有价值的知识。 一 管理和架构 做技术的同学一般有两条职业发展路径&#xff0c;横向的管理路线和纵向的技术路线。管理路线对应的是管理岗&#xff0c;讲究的是排兵布阵&#xff0c;通过各种资源的优化配置发挥价值。技术路…

(Linux驱动学习 - 10).MISC驱动实验

一.MISC介绍 1.MISC定义 misc 的意思是混合、杂项的&#xff0c;因此 MISC 驱动也叫做杂项驱动&#xff0c;也就是当我们板子上的某 些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱 动&#xff0c;通常嵌套在 platform 总线驱动中&…

智能贴身监测,健康生活建议,圆道妙医智能手表体验

如今热衷于运动和健康生活的爱好者越来越多&#xff0c;相关的赛事等活动也是逐年增多&#xff0c;很多朋友为了能够直观的了解自己的健康状况&#xff0c;都会配备一款智能手表&#xff0c;这样戴在身上就可以随时了解自己的心率、血氧等数据。最近我尝试了一款圆道妙医推出的…

MobaXterm连接Cloudflare Tunnel内网穿透的SSH

背景 如官方文档所示&#xff0c;Cloudflare Tunnel要求我们对SSH客户端进行配置&#xff0c;使本地的cloudflared软件代理SSH才能连接。 存在问题 由于MobaXterm的Session实质为嵌入式PuTTY&#xff0c;不使用OpenSSH样式的配置文件&#xff08;即~/.ssh/config&#xff09…

SpringBoot框架下购物推荐网站的设计模式与实现

3系统分析 3.1可行性分析 通过对本东大每日推购物推荐网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本东大每日推购物推荐网站采用JAVA作为开发语言&…

对层级聚类树进行模块分割,定位基因在哪个模块中

拷贝数据到 ImageGP (http://www.ehbio.com/Cloud_Platform/front/#/analysis?pageb%27Ng%3D%3D%27)&#xff0c;并设置参数. ID untrt_N61311 untrt_N052611 untrt_N080611 untrt_N061011 trt_N61311 trt_N052611 trt_N080611 trt_N061011 ENSG000…

【ROS2实操二】服务通信

简介 服务通信也是ROS中一种极其常用的通信模式&#xff0c;服务通信是基于请求响应模式的&#xff0c;是一种应答机制。也即&#xff1a;一个节点A向另一个节点B发送请求&#xff0c;B接收处理请求并产生响应结果返回给A。比如如下场景&#xff1a;机器人巡逻过程中&#xff0…