10

干货 | 借助Linux shell脚本搞定一个开机异常问题

 2 years ago
source link: https://www.eefocus.com/embedded/519212
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

哈喽,大家好,我是小G。

今天记录一篇,接文章《Linux shell 语法 if [ $? == 0 ] 详细》末尾 mark 过的,借助于Linux shell 脚本最终搞定一个低概率开机异常问题。

因为概率不开机异常问题本身涉及问题点比较杂比较多,所以这篇笔记中,我主要把 Linux shell 脚本核心的使用思路和涉及到一些关键知识点,梳理抽离了出来,比如文章最后有 Android 和 Linux wakelock 的知识等  ,希望对大家有用。

另外,还是之前文章老话,共勉:linux shell 功能很强大,平时工作主要在linux下做开发,不管是调试问题、还是自动化测试需要,或者是阅读项目代码中的编译构建脚本,基本上都避免不了shell 语法规则的学习。shell就是生产力,更多 linux命令系列 文章,可见文章末尾。

【问题描述】

有一个用户碰到机器无法开机问题,但用户当时默认没有开启log,当时也着急使用机器,所以进行了强行重启恢复。没有了现场,也没有了日志。无从分析,只知道是开机过程问题。而且只有一个用户碰到。

【初步分析】

1、和用户沟通了解使用场景,属于正常重启机器;重启之前没有什么特别、特殊操作;

2、问题发生在开机阶段,结合用户使用数量和内部测试机器数量,总体的开机和重启次数并不低,所以可以初步判断应该是个低概率问题,需要压测复现;

3、用户默认是user版本,可以获取到信息和调试手段有限,需要在内部使用userdebug版本进行问题复现;但同时也担心如果只是user版本的问题userdebug不会出现,就不好搞了。不过还是考虑优先使用userdebug进行看下情况;

4、接下来面临的问题就是如何进行压测了。人工显然效率太低了。根据用户和测试情况估计看,至少应该也在几百次以上估计看能复现。

再做假设,即使我们通过人工重启,废了九牛二虎复现了问题,最终定位到了问题,修改后,想压测确认看下收益,此时如果也还通过人力进行压测验收,显然前前后后,依靠人工操作的时间成本太大,效率也很低。

所以需要优先考虑是否可以实现一个脚本,自动让机器反复重启,而且能够动态过滤开机过程中的日志信息,进行判断是否发生了异常,如果有异常出现则停止重启操作,触发dump,如果日志没有预期异常,则一直进行反复重启测试,直到问题出现。

【解决方案】

问题原因比较复杂,先说下最终分析结果:

最后确认到是在开机过程中,有一个系统相关服务因另外一个概率干扰因素,导致该系统服务加载异常,然后导致出现了无法正常开机的问题。该篇文章重点主要关注,如何通过脚本,实现自动化重启压力测试,进行问题复现;其他不是重点。

下面就针对自动化方案,详细说下情况:

1、实现自动重启,首先想到,通过 bat 脚本,监控 adb devices 情况,然后 adb logcat 导出日志,然后在 bat 脚本中判断是否存在预期的异常日志,决定是否需要通过 adb reboot 命令重启机器。但如果在 windows 下依靠 bat 窗口执行命令,可能会受到其他任务事件的干扰,抢占掉当前工作窗口,此时就必须要人工参与了。这种方式还是不靠谱,所以暂时还是考虑放弃。

2、如果我们把需要做的事情做成 .sh 脚本,push 到机器里面来执行命令,这样就能避免外部窗口执行命令被干扰的情况了。理想很丰满,但现实很骨感,想要成功调起自己的 .sh,还需解决 selinux 权限,以及考虑和调试 init.rc调起 .sh的时机(想要获取异常服务加载情况需要在被监控的服务之后调起),当然解决这两点并不是难,但需要时间(后续会专门写一篇如何增加自己的sh,并解决selinux问题,并成功调用它)。

而此时我们的目标是需要尽快的进行压测重启,赶紧复现问题,所以就想,如果机器里要是有可以直接能使用的测试脚本就好了,答案是 yes 的。

抱着这种想法,最终通过多个类似,如下命令找到了一个脚本:

root:/ # find ./ -name "*test*.sh"
./product/etc/init.xxx.testscripts.sh

针对这个现成的 .sh 进行了大概跟踪分析,可以确认它是可以直接供我们使用、改造的,被调关系和相关服务情况如下:

root:/ # cat /vendor/etc/init/hw/init.xxx.rc | grep testscripts
service xxx-testscripts /system/bin/sh /product/etc/init.xxx.testscripts.sh
    seclabel u:r:xxx-testscripts:s0
service yyyyy 
    start xxx-testscripts

所以基本可以确认,我们是可以直接借用 init.xxx.testscripts.sh实现自己的脚本内容的。

先 pull 出来该脚本,改造如下,主要实现结合日志情况让自动化进行重启测试:

mylog=`logcat -d | grep "mytest"`
echo "$mylog" >> /sdcard/mytest.log

mylog_success=`echo $mylog | grep "succeeded"`
if [ -n "$mylog_success" ]; then
    # Append the successful log
    echo "Current test is succeeded" >> /sdcard/mytest.log
    # hold wakeup
    echo test > /sys/power/wake_lock
    #must remember add sleep, so that we can have time to manually control
    sleep 60
    #call reboot
    reboot
else
    # if failed, $mylog_success will is empty value, Append the failed log
    echo "Current test is failed" >> /sdcard/mytest.log
fi

脚本写好后,push 调试看了下,验证有效。

关键说明:

1、常用 logcat  参数:

--"-d"选项 : 将缓存的日志输出到屏幕上,并且不会阻塞
--"-t"选项 : 输出最近的几行日志,输出完退出,不阻塞
--"-b"选项 : 加载一个日志缓冲区, 默认是 main,可以重新制定,如 logcat -b kernel

2、注意,这里 echo 使用的是 >> ,注意 > 和 >> 的区别:

echo > 会覆盖原来的内容
echo >> 会追加内容到末尾

3、可能有人注意到,脚本有增加 sleep,这样做的目的在反复重启的过程中,是给我们手动参与预留一些可控时间,这个是必须要的。

假如我们将 sleep去掉,重启后,可能 adb 还没有 ready 可用时,sh 脚本就被 rc 触发直接进行 reboot 了,这样机器就失控了,永远无法再有时间机会修改和控制脚本,reobot就无法停下来了,所以说是必须的。测试开始前,我们可以先备份机器中原有的 init.xxx.testscripts.sh,需要停止测试时,在 sleep 间隙,将原有脚本 push 进去即可还原:

# 导出机器中原有的 init.xxx.testscripts.sh
adb pull /product/etc/init.xxx.testscripts.sh

# 重命名备份为 init.xxx.testscripts.backup.sh,还原时直接 push 覆盖
adb root
adb remount
adb push init.xxx.testscripts.backup.sh /product/etc/init.xxx.testscripts.sh

以上操作,如果想通过脚本push实现,一定记得 root 和 remount 后增加 adb wait-for-device,防止端口不在却执行了命令。

4、在脚本中,加了如下语句,主要是为了防止系统睡眠,避免出现脚本没有正常被调用执行;

echo "test" > /sys/power/wake_lock

/sys/power/wake_lock ,用户程序向文件写入一个字符串,即可创建一个 wakelock,该字符串就是wakelock的名字。该 wakelock 可以阻止系统进入低功耗模式。

/sys/power/wake_unlock ,用户程序向文件写入相同的字符串,即可注销一个wakelock。

当系统中所有的 wakelock 都注销后,系统可以自动进入低功耗状态。

Android 的 wakelock,真是一个lock,用户程序创建一个wakelock,就是在系统 suspend 的路径上加了一把锁,注销就是释放这把锁。直到 suspend 路径上所有的锁都释放时,系统才可以 suspend。

而 Kernel 的wakelock,是基于wakeup source实现的,因此创建 wakelock 的本质是在指定的wakeup source上activate一个wakeup event,注销wakelock的本质是deactivate wakeup event。因此,/sys/power/wake_lock/sys/power/wake_unlock两个sysfs文件的的功能就是:

写wake_lock(以wakelock nametimeout时间<可选>为参数),相当于以wakeup source为参数调用__pm_stay_awake(或者__pm_wakeup_event),即activate wakeup event;

写wake_unlock(以wakelock name为参数),相当于以wakeup source为参数,调用__pm_relax

读wake_lock,获取系统中所有的处于active状态的wakelock列表(也即wakeup source列表)

读wake_unlock,返回系统中所有的处于非active状态的wakelock信息(也即wakeup source列表)。

ref:http://www.wowotech.net/pm_subsystem/wakelocks.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK