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

文章目录

    • 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 ]]; dokey="$1"case $key in-i|--interval)INTERVAL_MS="$2"shift # past argumentshift # past value;;-f|--file)FILENAME="$2"shiftshift;;-t|--time)RUN_TIME_SECONDS="$2"shiftshift;;--disable_selective_logging)SELECTIVE_LOGGING=falseshift;;-h|--help)usage;;*)echo "Unknown option: $1"usage;;esac
done# Validate parameters
if ! [[ "$INTERVAL_MS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_MS" -le 0 ]; thenecho "Error: Interval must be a positive integer."usage
fiif ! [[ "$RUN_TIME_SECONDS" =~ ^[0-9]+$ ]] || [ "$RUN_TIME_SECONDS" -le 0 ]; thenecho "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=0declare -a pre_jump_timestamps=()# Create or clear the log file
if ! > "$FILENAME"; thenecho "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 stringcurrent_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=falseif [[ $last_timestamp_us -ne 0 && $second_last_timestamp_us -ne 0 && $SELECTIVE_LOGGING == true ]]; thenif [[ $current_timestamp_us -lt $last_timestamp_us ]]; then# Time regression detectedtime_jump=trueelse# Check if the interval is too small based on second last timestampexpected_interval_us=$(( INTERVAL_MS * 1000 ))actual_interval_us=$(( current_timestamp_us - second_last_timestamp_us ))threshold_us=$(( expected_interval_us * 15 / 10 ))  # 1.5x thresholdif [[ $actual_interval_us -lt $threshold_us ]]; thentime_jump=truefififi# Update timestampssecond_last_timestamp_us=$last_timestamp_uslast_timestamp_us=$current_timestamp_us# Add current time to pre_jump_timestamps arraypre_jump_timestamps+=("$current_time_str")# Keep array length at most PRE_JUMP_RECORDif [[ ${#pre_jump_timestamps[@]} -gt $PRE_JUMP_RECORD ]]; thenpre_jump_timestamps=("${pre_jump_timestamps[@]: -$PRE_JUMP_RECORD}")fiif [[ $SELECTIVE_LOGGING == true && $time_jump == true && $in_jump_mode == false ]]; then# Detected a time jump, enter jump modein_jump_mode=truejump_remaining=$POST_JUMP_RECORD# Log pre-jump timestampsecho -e "\n--- TIME JUMP DETECTED ---" >> "$FILENAME"for ts in "${pre_jump_timestamps[@]}"; doecho "$ts" >> "$FILENAME"done# Log current timestamp with [TIME_JUMP] markerecho "$current_time_str [TIME_JUMP]" >> "$FILENAME"elif [[ $in_jump_mode == true ]]; then# In jump mode, record post-jump timestampsecho "$current_time_str" >> "$FILENAME"jump_remaining=$((jump_remaining - 1))if [[ $jump_remaining -le 0 ]]; thenin_jump_mode=falsefielse# Normal mode: log every 500 timestampstotal_timestamps=$((total_timestamps + 1))if [[ $((total_timestamps % 500)) -eq 0 ]]; thenecho "$current_time_str" >> "$FILENAME"fifi# Check and perform log rotationcurrent_size=$(stat -c%s "$FILENAME" 2>/dev/null || echo 0)if [[ $current_size -ge $MAX_FILE_SIZE ]]; thennew_filename="${FILENAME}.$(date +"%Y%m%d%H%M%S")"if ! mv "$FILENAME" "$new_filename"; thenecho "Error: Unable to rotate log file to '$new_filename'."exit 1fiecho "Rotated log file to $new_filename"# Create a new log fileif ! > "$FILENAME"; thenecho "Error: Unable to create new log file '$FILENAME'."exit 1fi# Remove oldest log file if there are more than MAX_LOG_FILESlog_files=($(ls -t "${FILENAME}".* 2>/dev/null))if [[ ${#log_files[@]} -gt $MAX_LOG_FILES ]]; thenfor (( i=$MAX_LOG_FILES; i<${#log_files[@]}; i++ )); doif ! rm "${log_files[$i]}"; thenecho "Error: Unable to remove old log file '${log_files[$i]}'."fidonefifi# Replace awk with bash and printf to calculate sleep_timeinteger_part=$(( INTERVAL_MS / 1000 ))fractional_part=$(( INTERVAL_MS % 1000 ))# Ensure fractional_part is three digits with leading zeros if necessaryfractional_part_padded=$(printf "%03d" "$fractional_part")sleep_time="${integer_part}.${fractional_part_padded}"# Sleep for the specified intervalsleep "$sleep_time"
doneecho "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 ]]; dokey="$1"case $key in-i|--interval)INTERVAL_MS="$2"shift # 过去参数shift # 过去值;;-f|--file)FILENAME="$2"shiftshift;;-t|--time)RUN_TIME_SECONDS="$2"shiftshift;;--disable_selective_logging)SELECTIVE_LOGGING=falseshift;;-h|--help)usage;;*)echo "Unknown option: $1"usage;;esac
done# 验证参数
if ! [[ "$INTERVAL_MS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_MS" -le 0 ]; thenecho "Error: Interval must be a positive integer."usage
fiif ! [[ "$RUN_TIME_SECONDS" =~ ^[0-9]+$ ]] || [ "$RUN_TIME_SECONDS" -le 0 ]; thenecho "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=truetime_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 ]]; thentime_jump=truetime_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 ]]; thennew_filename="${FILENAME}.$(date +"%Y%m%d%H%M%S")"if ! mv "$FILENAME" "$new_filename"; thenecho "Error: Unable to rotate log file to '$new_filename'."exit 1fiecho "Rotated log file to $new_filename"# 创建新的日志文件if ! > "$FILENAME"; thenecho "Error: Unable to create new log file '$FILENAME'."exit 1fi# 如果日志文件超过MAX_LOG_FILES个,删除最旧的文件log_files=($(ls -t "${FILENAME}".* 2>/dev/null))if [[ ${#log_files[@]} -gt $MAX_LOG_FILES ]]; thenfor (( i=$MAX_LOG_FILES; i<${#log_files[@]}; i++ )); doif ! rm "${log_files[$i]}"; thenecho "Error: Unable to remove old log file '${log_files[$i]}'."fidonefi
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.mzph.cn/web/54984.shtml

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

相关文章

【LeetCode 算法笔记】164. 破解闯关密码

目录 问题描述冒泡排序 问题描述 问题来源 闯关游戏需要破解一组密码&#xff0c;闯关组给出的有关密码的线索是&#xff1a; 一个拥有密码所有元素的非负整数数组 password密码是 password 中所有元素拼接后得到的最小的一个数 请编写一个程序返回这个密码。 示例 1: 输入…

【linux】信号(下)

8. 阻塞信号 (一)信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)进程可以选择阻塞 (Block )某个信号被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作(即被阻塞的信…

Vue中使px自动转化为rem配置(h5适配问题)

以下方法为px自动转换rem&#xff0c;顾名思义&#xff0c;配置完成后&#xff0c;不用再关心rem换算等等&#xff0c;只需按照设计稿的px值写入即可&#xff0c;当你保存后PostCSS插件会自动将px转换成所配置的rem值&#xff0c;并且你在浏览控制台观测界面时你会发现你在代码…

如何在阿里云一键部署FlowiseAI

什么是FlowiseAI FlowiseAI 是一个开源的低代码开发工具&#xff0c;专为开发者构建定制的语言学习模型&#xff08;LLM&#xff09;应用而设计。 通过其拖放式界面&#xff0c;用户可以轻松创建和管理AI驱动的交互式应用&#xff0c;如聊天机器人和数据分析工具。 它基于Lang…

c++速成之从string类中获取那些知识

温馨提示&#xff1a;本篇文章依旧是c速成系列的文章&#xff0c;因为从这里开始&#xff0c;内容就已经开始复杂&#xff0c;但博主还是以是什么&#xff0c;怎么用的原则继续给大家讲解知识点&#xff0c;希望大家能够耐心看完&#xff0c;并给博主留个三连&#xff0c;博主先…

Redis-缓存过期淘汰策略

缓存淘汰策略 生产上redis内存设置为多少 设置为最大内存的 3/4 redis 会占用物理机多少内存 默认大小是 0&#xff0c;64 位系统下表示不限制内存大小&#xff0c;32位系统表示 3G 如何设置修改redis内存大小 config get maxmemory 查看修改方式 配置文件 单位是字节 2.…

机器学习【金融风险与风口评估及其应用】

机器学习【金融风险与风口评估及其应用】 一、机器学习在金融风险评估中的应用1.提升评估准确性2.实现自动化和智能化3.增强风险管理能力4.信用评估5.风险模型6.交易策略7.欺诈检测 二、机器学习在金融风口评估中的应用1.识别市场趋势2.评估创新潜力3.优化投资策略4. 自然语言处…

深入Postman- 自动化篇

前言 在前两篇博文《Postman使用 - 基础篇》《玩转Postman:进阶篇》中,我们介绍了 Postman 作为一款专业接口测试工具在接口测试中的主要用法以及它强大的变量、脚本功能,给测试工作人员完成接口的手工测试带来了极大的便利。其实在自动化测试上,Postman 也能进行良好的支…

校车购票微信小程序的设计与实现(lw+演示+源码+运行)

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

Web前端高级工程师培训:封装自己的库

封装自己的库 课前准备 工具 编辑器 VSCode浏览器 Chorme 前置知识 Js基本知识 课堂主题 一、定义函数返还JQ对象 二、ready方法和原生节点处理 三、选择器器封装 四、封装JQ的eq方法 五、封装JQ的click方法 六、jQ中的链式操作 七、封装JQ的css方法 八、cssHooks扩…

软考《信息系统运行管理员》- 4.1信息系统软件运维概述

4.1信息系统软件运维概述 文章目录 4.1信息系统软件运维概述信息系统软件运维的概念信息系统软件的可维护性及维护类型对软件可维护性的度量可以从以下几个方面进行&#xff1a;软件维护分类&#xff1a; 信息系统软件运维的体系1.**需求驱动**2.**运维流程**3.**运维过程**4.*…

Vue 的 v-show 和 v-if 区别?

一、区别 v-show 和 v-if 是 Vue.js 中两种常用的指令&#xff0c;都可以用于控制元素的显示和隐藏&#xff0c;但它们有本质上的区别: 1)v-show 是通过控制元素的 Css display属性来显示或隐藏元素。无论条件是否为真&#xff0c;元素都会被渲染到 DOM中&#xff0c;只是通过…

斯坦福 CS229 I 机器学习 I 构建大型语言模型 (LLMs)

1. Pretraining -> GPT3 1.1. Task & loss 1.1.1. 训练 LLMs 时的关键点 对于 LLMs 的训练来说&#xff0c;Architecture&#xff08;架构&#xff09;、Training algorithm/loss&#xff08;训练算法/损失函数&#xff09;、Data&#xff08;数据&#xff09;、Evalu…

如何减少 Webpack 的打包体积

前端面试题包括ECMScript,TypeScript,Nodejs,React,Webgl,Webpack,Threejs等还在整理中&#xff0c;在线地址前端面试题&#xff0c;源码地址大家多多支持才有动力给大家分享更多好的面试题。 减少 Webpack 的打包体积是优化 Web 应用性能的重要步骤&#xff0c;尤其是在生产环…

两种方式创建Vue项目

文章目录 引言利用Vue命令创建Vue项目准备工作安装Vue CLI创建Vue项目方法一&#xff1a;使用vue init命令方法二&#xff1a;使用vue create命令启动Vue项目 利用Vite工具创建Vue项目概述利用Vite创建项目启动项目 结语 引言 大家好&#xff0c;今天我将向大家展示如何使用不…

鸿蒙富文本显示

1.使用 RichText 组件&#xff08;ArkTS&#xff09; 背景知识&#xff1a;在 ArkTS&#xff08;一种鸿蒙应用开发语言&#xff09;中&#xff0c;RichText组件提供了更强大的富文本显示功能。它允许设置不同的文本样式&#xff0c;包括字体、颜色、字号等多种属性。 Rich Te…

uniapp 如何引用icon 字体

平时使用阿里巴巴的iconfont字体需要下载字体到本地或通过网址引入字体才能使用&#xff0c;但有些情况可能不允许这么做&#xff0c;例如小程序与平常web开发中引入字体图标的方式不一样&#xff0c;必须先转为base64再引入&#xff0c;以下介绍iconfont字体转base64并引入使用…

【layui】多文件上传组件实现

插件预览效果&#xff1a; 需要引入layui的脚本文件layui.js和样式文件layui.css html代码&#xff1a; <div class"layui-input-block"><div class"layui-upload-list"><table class"layui-table"><colgroup><col…

easyexcel多sheet导出(唯一能用)

1&#xff0c;response流header, contentType等设置跟单sheet一样 2&#xff0c;上代码 Data public class SheetModel<T> { private String sheetName; private Class<T> clazz; private List<T> data; } ExcelWriter writer EasyExcel.write(resp.ge…

vue3中如何更改当前类的文件名称

首先&#xff0c;使用script指定文件名称 <template><div class"person"><h2>姓名&#xff1a;{{ name }}</h2><h2>年龄&#xff1a;{{ age }}</h2><button click"showTel">查看联系方式</button><bu…