Caveman.Work Blog

  • Blog
  • Contact
  • Archives

Software Performance Engineering

Posted on 2019-07-12

软件性能工程 「Software Performance Engineering」

本文使用不到五分钟的阅读时间来阐述软件性能工程里的关键知识点,有助于大家在规划软件项目时,通过引入性能工程以提高软件项目的可用性与市场成功率。简单来说,软件性能工程是通过将性能指标维度纳入到软件开发周期中的各关键节点,通过事前规划与各里程碑节点上的关键验收,来追求最终交付的软件质量是符合设计预期的。

它的价值在哪里?

在开发软件时(不包含简单的脚本类程序),有时候会优先考虑把功能做完,即所谓的Make it run first。此时大部分的精力都放在了功能开发上,对性能指标的考虑是放在第二,甚至第三位的。有时候甚至不考虑性能,只有当客户反馈了问题之后才会着手去优化代码。

这种做事方法就不是一个工程师思维的做事方式,更像是产品经理的思维。先把东西弄出来,试一试,看看效果怎么样。这种是在小规模,或者影响可控的范围内是可行的,但这并不是常态。更常见的情况是,工程师根据比较明确的需求通过项目管理(如敏捷开发)的方式进行工程开发,为了使交付效率与质量最高,必须遵照一定的工程化方式做事情。性能工程能帮助项目解决的难题如下:

  • 可避免性能瓶颈是因软件架构引起的问题,这是典型的低概率但后果严重的错误。一旦遇到这类问题,通过优化几处热点代码是无法根治的,需要彻底的重构。
  • 用户反馈的问题无法通过有效的手段、日志来定位问题,只能通过成本最高的方式,也就是让用户复现问题的方式来定位问题。
  • 硬件容量规划时无法根据之前的项目经验进行量化分析。

SPE Lite版执行流程

具体怎么执行呢?性能工程是一个完整的工程学科,这篇短文当然无法完整的描述所有的细节,所有的知识点。就着够用就好的原则,仅介绍最关键的执行步骤,读者可感受下画风。

  1. 通过用户调研与竞品分析,定义关键用户性能指标,如:吞吐量,传输速度,界面刷新帧率
  2. 设计可满足业务需求与性能指标的软件架构
  3. 对性能指标建模
    • 如果是排队系统时,使用排队论建模工具
  4. 开发性能指标测试工具与测试用例
  5. 开发用于记录性能指标变化的监控器
    • 制定性能数据本地存储规则
    • 制定性能数据云端回传规则
  6. 制作性能数据可视化表盘

写在最后

  • 做事讲究方法,套路。这些方法,用现在的话来说就是各种思维模式。
  • 从之前火热的互联网思维,到近几年流行的产品思维,本质上都是做事方式,其目的也非常简单,那就是更好地做事。
  • 通过更好地做事,才有可能做出优秀的产品,只有优秀产品才有可能提高市场竞争力。当产品有了市场竞争力,才有可能赢得其他方面的成功,比如商业。

Data science for mobile OS system optimization

Posted on 2019-06-01

Last update 201906016

做些铺垫

当今嵌入式设备OS系统优化面临的挑战难题有:

  1. 造成发热,卡顿,待机时间差的主要瓶颈是什么?
  2. 当设置内核,JVM,资源管理功能的参数时,到底该怎么设置才合理?
  3. 如何全面的评估某个算法的优化效果?

想要回答上面几个问题,目前的做法是根据几个有效日至或者通过本地复现的手段来寻找线索。虽然可以回答问题,也有理有据,但他的准确度是值得怀疑的。 最根本的原因是样本量太少!
以问题3为例,假设是在评估某个CPU Affinity分配算法。可以通过单个或多个的benchmark结果来评估算法效果,但还是有可能发生“不知道的不知道”。

为了避免发生“不知道的不知道”,我们可以采用如下方法:

  1. 构建实验组与对照组,通过对比实验的方式量化优化效果。评估单维度指标,也可以同时评估多个维度的指标变化。
  2. 量化不同算法下的系统主要瓶颈变化,用于排除优化一个场景反而带来另一个场景的性能降低。

为了实现方法1,2,我们应该具备如下能力:

  • 低负载的多维度数据监控与存储器。
  • 大数据分析能力。
  • 全栈业务领域知识。

数据科学

那我们怎么才能系统化的建设这方面能力呢?下面引申出本文章的主题,数据科学。

Data science is a multi-disciplinary field that uses scientific methods, processes, algorithms and systems to extract knowledge and insights from structured and unstructured data. - 维基百科

这里的关键词是 scientific method,extract knowledge and insights。使用科学的方法,从数据中获得洞见。随着大数据技术的完善与普及,通过利用大量采集而来的数据来认识事物本质成为了可能。而我们习惯的思考模式是从个别现象中寻找共性,然后再用大量事实来验证此共性是否准确。 第二种方法进展缓慢,且成本也大。但第一种就不一样了,随着数据的存储跟计算越来越便宜,我们可以直接使用数据获得有用的洞见。

若想成功应用数据科学,需要兼备如下多学科知识跟技能:

  • 概率与统计学
  • 机器学习
  • 业务领域知识
  • 计算机科学
  • 代码编程
  • 数据可视化
  • 表达与沟通

应用数据可学的步骤

针对系统优化领域,我们应该怎么使用数据科学方法获得洞见呢? 我认为以下6个步骤是必须的,而且严格按照顺序执行:

  1. 定义分析目的
  2. 定义有效的评估指标
  3. 数据收集器的设计与实现
  4. 根据分析目的,选择对应的分析模型来分析数据
  5. 寻找洞见
  6. 根据洞见反推设计,并得到改善

[步骤1] 定义分析目的

分析目的不同设计的指标与分析方法也不同,常见的分析目的有:

  • 趋势观察
  • A/B Test
  • 异常时问题分析
  • 瓶颈分析
  • 异常预警

[步骤2] 定义有效的评估指标

这步骤主要考验领域知识的掌握深度,因为指标的主要来源以及服务目标就是来自于业务。指标定义要优先于优化方案评估,目的在于你的优化方案要为指标的优化而努力。这时候指标更像是一种优化目标的量化方法,如果优化目标无法量化也意味着你的不明白你到底要做什么。 好指标应当结合业务需求,技术需求的不同角度来综合设计。 比如谷歌的WSMeter指标,阿里数据中心的WorkDone指标。通过思考指标的定义,也促使自己思考优化的方向。一个好的指标,像个灯塔一样,非常准确地指引着优化目标,而一个坏的指标往往会导致顾此失彼的结果。

提到指标,就不得不提到北极星指标(North Start Metric),又被称为唯一重要的指标。它是用户增长领域里的概念,意指像北极星一样高高闪耀在天空中,指引着全公司上上下下向着同一个方向努力。虽然关注领域有点不同,但是所要达成的目标是一致的,即能够准确量化最关键的业务目标。读者可以感受下制定北极星指标时需要遵守的几个标准:

标准1:你的产品的核心价值是什么?这个指标可以让你知道你的用户体验实现了这种价值吗?
标准2:这个指标能够反映用户的活跃程度吗?
标准3:如果这个指标变好了,是不是能说明你的整个公司是在向好的方向发展?
标准4:这个标准是不是很容易被你的整个团队理解和交流呢?
标准5:这个指标是一个先导指标,还是一个滞后指标?
标准6:这个指标是不是一个可操作的指标?

[步骤3] 数据收集器的设计与实现

收集器需要参考指标的定义来设计,有些数据的采集难度或者成本较高,这时候可以通过巧妙的指标来弥补这部分缺点。有时候因为现有机制缺少相关的数据提供方式,需要单独实现一套高效率的收集机制,特别是牵涉到内核级别的数据时往往都会采用定制方法。
综合来说主要原则有两个:

  • 数据能够准确的代表业务,这是基础中的基础。
  • 收集数据时工作负载要小,需要控制在一定影响范围内。如果无法保证性能的话需要采用发布策略在尽可能不打扰用户的前提下抓取数据。
  • 数据能完整地涵盖业务变化,宁可多收集一些冗余数据但不能容忍有遗漏。

[步骤4] 根据分析目的,选择对应的分析模型来分析数据

为了达成有效的数据实验,需要熟悉各种数据挖掘技术(算法),除了工作原理之外还有就是他们的最佳应用场景。根据分析目的为分类的话:

1:分析目的为趋势观察时:
只需要将收集上来的数据进行ETL之后使用可视化工具展示就可以了,这里的难点在于可视化图表的选择上,选择原则为:

  • 尽量使读者看图知意,不需要过多的猜测与思考以免造成误解。
  • 可视化图表需要展示完整的数据。

2:分析目的为异常分析时:
基本以非结构化数据为主,分析主要结合领域知识跟专家系统做分析平台。经常会被产品化团队当做主要分析目的使用。

3:分析目的为瓶颈分析与异常预警时:
基本以结构化日志为主,使用的算法主要来自机器学习领域。

常见的数据挖掘技术有:

  • 决策树(Decision Tree)
  • 神经网络(Neural Network)
  • 回归(Regression)
  • 关联规则(Association Rule)
  • 聚类(Clustering)
  • 贝叶斯分类方法(Bayesian Classifier)
  • 支持向量机(Support Vector Machine)
  • 主成分分析(Principal Components Analysis)
  • 假设检验(Hypothesis Test)

[步骤5] 寻找洞见

这一步重点在于使用业务知识来解读步骤4中生成的数据结论。从经验上来看,往往决定胜负的并不是数据挖掘技术,而是来自于对业务的深刻理解上。这时候数据结果起到辅助判断的作用,所以万不可盲目崇拜数据技术而不重视业务知识。特别需要注意的是当业务专家与算法计算结果发生冲突的时候,这可能是一个潜在的优化点。

[步骤6] 根据洞见反推设计,并得到改善

前面的努力都是为了这一刻,从步骤5中获得的洞见将会指导我们重新审视现有的方案,或者验证我们的猜想。这一步是我们的终极目的,只有反哺到了现有业务,才是真正有效的一次数据应用实践。但这并不意味着结束,通过此步骤得到结果,我们可以发起下一轮的实验,即返回到了步骤1。

写在最后

  • 人的认知提升是一个螺旋上升的过程,不要指望一把命中目标,我们应该具备的思维方式是迭代式改进的思维模式(或者说是演化思维)。在后果可控的前提下可以犯错,可以做实验,这一次实验是要站在前一次实验的基础上。通过这种方法不敢保证一定会成功,但它是成功概率最高的一种做事方法。
  • 理论知识往往都很好学,很好理解,难点就在于时刻把理论应用到现实中,从现实中验证理论。有点像学习经济学思维一样,他并不要求你能背诵多少个经济学名词,反而更关心经济学规律如何在现实中得到应验,以及如何使用规律构建规则使社会运行效率最大化。
  • 未来是属于数据间的竞争,而数据竞争的源头来自于指标的定义。一个好的指标定义很大程度上决定了,通过数据科学的方式优化业务的效果,务必要给予高度重视,需要时刻反思指标的正确性。
  • 经济学原理指出,当交易成本近乎为零时,谁更能利用好资源,那资源就归谁。同样道理,数据本身只是存储在服务器里,谁能挖掘到洞察,谁就得到金子。

Reference

  1. 张溪梦:首席增长官:如何用数据驱动增长
  2. 卢辉:数据挖掘与数据化运营实战:思路,方法,技巧与应用

eBPF on Android

Posted on 2019-01-29

Last update 20190601

做些铺垫

本文假设读者已掌握如下内容:

  1. 熟悉Linux内核编译方法。
  2. 阅读过博文eBPF架构优势及其应用方向上的畅想。
  3. 熟悉Git操作
  4. 熟悉CMake,LLVM,Clang等编译工具

在博文eBPF架构优势及其应用方向上的畅想中有提到eBPF的执行流程,这套在主机系统(泛指基于x86的Linux distribution)上直接apt install或者源码编译安装就可以了。但是在android上怎么执行呢? 现在大部分android设备运行在基于arm的处理器架构上,我们熟悉的高通,MTK,华为海思都属于arm处理器架构。x86上的eBPF工具栈(如前文所述,这里仅指BCC&BPFTrace)程序是无法直接在arm处理器上执行的,需要所谓的交叉编译技术才可以。除了程序本身之外,它所依赖的基础库如libc也同样需要交叉编译才能正确工作。

运行在android时的难题

  1. BCC及BPFTrace使用的是基于CMake的编译方式,与android使用的gradle,android BP的编译系统是不一致的。
  2. BCC项目中以python 包为基础,那意味着需要有python运行环境,这在anroid里也是没有的。

可行的思路

  1. 没条件就创造条件,将BCC&BPFTrace强行适配到android编译环境,使其可以直接运行在android上下文中。中间涉及的依赖,冲突问题需要手动修改。
  2. 添加中间层,由host端生成的的bytecode通过adb通道派发给client端,具体执行由client端的常驻进程完成。
  3. android端运行某个linux distribution环境(如Debian,Ubuntu),在手机端编译与安装BCC&BPFTrace。
  4. 参考BCC&BPFTrace设计思路,依照android的架构实现一套类似功能程序,部分采用BCC&BPFTrace项目代码。

综合利弊之后,本文使用方案3。缺点就是对android的环境要求比较高,它需要:

  1. 手机能够root,而且可以将data分区remount成可读写。
  2. android kernel 版本要求在4.9及以上。
  3. 具有编译android kernel的环境。

步骤1 编译带必要功能的内核

通过手动编内核实现以下两个目的:

  • 使能eBPF相关功能(如果已经开启可以跳过此步骤)。
  • 获取特定kernel的头文件。BCC会用到此头文件中的结构体来解析eBPF的返回数据。

1:开启以下内核配置到项目_defconfig文件中

1
2
3
4
5
6
7
8
9
10
11
CONFIG_BPF=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_BPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENT=y
CONFIG_DEBUG_PREEMPT=y
CONFIG_PREEMPTIRQ_EVENTS=y
CONFIG_FTRACE_SYSCALLS=y

2:根据项目情况编译内核

  • 如果你有完整android项目代码的话可以make bootimage编译出内核
  • 否则配置好交叉编译环境后单独编译内核。

关于获取Linux kernel 头文件:

  • 如果你直接使用交叉编译环境来编译内核的话可以忽略这段内容。
  • 如果你的内核改动比较少,算是比较”干净”的话可以直接使用别人已经打包好的头文件包。不过这种情况比较少见,嵌入式的linux 内核基本被芯片厂或手机厂有所修改。
  • 如果你是用android树来编译内核的话,需要手动编译头文件因为android的打包结构并不保留头文件。编译方法如下:
    1. cd to kernel tree
    2. export ARCH=arm64
    3. export CROSS_COMPILE=aarch64-linux-gnu- (任意交叉编译器都可以)
    4. make boardname_defconfig
    5. make -j6

步骤2 安装debian-arm到Android

Github有叫adeb项目,它的功能是将debian-arm整个固件push到android设备的/data目录下,然后并通过本地shell的配合实现了debian环境下shell。也就是debian能支持的功能他都能支持,只是没有屏幕,只能通过终端控制。当然也支持apt命令,通过修改源(apt source)之后下载安装社区提供的各种软件。

  1. 首先下载adeb项目 git clone https://github.com/joelagnel/adeb.git
  2. 然后执行安装命令,此时需要手机已经是root,并且确保剩余空间至少大于300MB。adeb的其他命令具体参考reference guide,参考引用2。
  3. adeb prepare –full –kernelsrc /path/to/kernel-source // 步骤1中提及的kernel路径
  4. adeb shell

即可进入到基于debian运行环境的shell。 他相比android区别在于只是利用了android中运行的linux kernel而其他标准库之类(bionic,linker等)都替换成debian所提供的libc及linker。安装过程及运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$  adeb prepare --full --kernelsrc ./msm-4.9_valina_sdm845       
|--------------|
| adeb: v0.99g |
|--------------|
16:01:15 - INFO : Looking for device..
16:01:15 - INFO : Preparing device...
16:01:15 - INFO : Doing a full install.
16:01:15 - INFO :
16:01:15 - INFO : Downloading Androdeb from the web...
16:01:15 - INFO :
16:01:15 - INFO : No repository URL provided in enviromnent. Attempting to auto-detect it
16:01:15 - INFO : Detected URL: github.com/joelagnel/adeb/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 610 0 610 0 0 589 0 --:--:-- 0:00:01 --:--:-- 589
100 295M 100 295M 0 0 400k 0 0:12:36 0:12:36 --:--:-- 541k
Archive: /tmp/tmp.A2epoY9AOr/androdeb-fs.tgz.zip
inflating: /tmp/tmp.A2epoY9AOr/androdeb-fs.tgz
16:01:53 - INFO : Building and updating kernel headers from kernel source dir (./msm-4.9_valina_sdm845)
16:01:58 - INFO : Using archive at /tmp/tmp.A2epoY9AOr/androdeb-fs.tgz for filesystem preparation
16:01:58 - INFO : Pushing filesystem to device..
16:01:09 - INFO : Pushing addons to device..
16:01:10 - INFO : Unpacking filesystem in device..
16:01:30 - INFO : Storing kernel headers into androdeb /kernel-headers/
16:01:57 - INFO : All done! Run "adeb shell" to enter environment

$ adeb shell

##########################################################
# Welcome to androdeb environment running on Android! #
# Questions to: Joel Fernandes <joel@joelfernandes.org> #
#
Try running vim, gcc, clang, bcc, git, make, perf etc #
or apt-get install something. #
##########################################################

root@localhost:/#

AOSP项目源码仓库中的 external目录 下面谷歌已经集成了adeb项目,如果有AOSP源码的话可以直接使用项目中的源码。这部分更新还未集成到Android P,应该是会随着Android Q一起发布。

Hello world

例1

1
2
3
4
5
6
7
8
root@localhost:/usr/share/bcc/tools# vfsstat 
TIME READ/s WRITE/s CREATE/s OPEN/s FSYNC/s
08:45:23: 98 369 0 5 0
08:45:24: 287 261 0 144 0
08:45:25: 115 129 0 49 0
08:45:26: 253 125 0 92 0
08:45:27: 326 272 0 74 0
08:45:28: 217 229 0 61 0

vfsstat(Virtual FileSystem Stats)可以查看下发到虚拟文件系统层的所有IO请求,如果看到以上结果就说明大功告成啦!过程中如果出现问题的话可以参考引用3。我自己遇到过kernel head不匹配与没有开启eBPF导致的错误。
预编译好的bcc工具集目录在 /usr/share/bcc/tools, 目前将近有100多个小工具。

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@localhost:/usr/share/bcc/tools# tcpdrop 
TIME PID IP SADDR:SPORT > DADDR:DPORT STATE (FLAGS)
08:54:36 3257 6 ::ffff:172.28.140.149:80 > ::ffff:183.60.137.144:46922 ESTABLISHED (ACK)
tcp_drop+0x0
tcp_rcv_established+0x2d4
tcp_v4_do_rcv+0x198
tcp_v4_rcv+0xb54
ip_local_deliver_finish+0x10c
ip_local_deliver+0x108
ip_rcv_finish+0x168
ip_rcv+0x344
__netif_receive_skb_core+0x5a8
__netif_receive_skb+0x38
process_backlog+0xd0
net_rx_action+0x258
__softirqentry_text_start+0x15c
do_softirq+0x70
netif_rx_ni+0x80
hdd_rx_packet_cbk+0x438
$x+0x310
$x+0x1e8
kthread+0xf4
ret_from_fork+0x10

查看TCP 掉包时的内核路径以推测掉包原因(TCP 掉包路径非常多,只能打印堆栈来诊断了)。

步骤3(可选) 源码编译安装最新版BCC与BPFTrace

默认的安装方式虽然简单但是所使用的工具版本比较老旧,为了体验最新功能可以下载最新代码并编译安装。需要提示的是以下操作都是在adeb shell中执行,也就是所有操作都在手机端完成,包括源码下载,编译与安装。

安装BCC

  1. git clone https://github.com/iovisor/bcc.git // 下载bcc项目代码
  2. cd bcc && rm -rf build && mkdir -p build && cd build //在bcc目录下创建build目录,用于代码编译。
  3. export CC=clang-6.0 // 设置C编译器
  4. export CXX=clang++-6.0 //设置C++编译器
  5. cmake .. -DCMAKE_INSTALL_PREFIX=/usr //运行环境检查
  6. make -j4 //编译
  7. make install //安装

安装后的tools路径为”/usr/share/bcc/tools”,运行cachestat检查下是否安装成功。

可能出现的错误:
BCC 20190129版本中运行上面命令时会出现如下错误:

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "/usr/share/bcc/tools/cachestat", line 20, in <module>
from bcc import BPF
File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 30, in <module>
from .syscall import syscall_name
File "/usr/lib/python2.7/dist-packages/bcc/syscall.py", line 387, in <module>
raise Exception("ausyscall: command not found")
Exception: ausyscall: command not found

解决方法:
安装auditd程序

apt install auditd

如果提示没有找到auditd命令的话,需要手动更新下source list。推荐将 “deb http://ftp.de.debian.org/debian stretch main” 添加到”/etc/apt/sources.list”文件后执行更新。

apt update

安装 BPFTrace

  1. git clone https://github.com/iovisor/bpftrace.git // 下载bpftrace项目代码
  2. cd bpftrace && rm -rf build && mkdir -p build && cd build //在bpftrace目录下创建build目录,用于代码编译。
  3. export CC=clang-6.0 // 设置C编译器
  4. export CXX=clang++-6.0 //设置C++编译器
  5. cmake -DCMAKE_BUILD_TYPE=Debug ../ //运行环境检查
  6. make -j4 //编译
  7. make install //安装

可能出现的错误1:
无法找到”BPF_FUNC_get_current_cgroup_id”定义!

错误原因是我用的4.9内核中还没有这个定义,是commit 22110ad25b51b0e1f1ece4fcdf21a3738391f018中引入的功能。如果你的内核也是4.9,或者提示没有定义的话可以单笔回退这个提交。

git revert 22110ad25b51b0e1f1ece4fcdf21a3738391f018

可能出现的错误2:
无法找到”bpf_create_map”,是否使用”bcc_createmap”替代?
这是因为bpftrace依赖bcc的库函数,而这个库函数中使用的是bcc开头。规避办法是将bpf
相关调用修改成bcc_,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
diff --git a/src/attached_probe.cpp b/src/attached_probe.cpp
index 1837b6a..de9c6e0 100644
--- a/src/attached_probe.cpp
+++ b/src/attached_probe.cpp
@@ -331,7 +331,7 @@ void AttachedProbe::load_prog()
for (int attempt=0; attempt<3; attempt++)
{
- progfd_ = bpf_prog_load(progtype(probe_.type), namep,
+ progfd_ = bcc_prog_load(progtype(probe_.type), namep,
reinterpret_cast<struct bpf_insn*>(insns), prog_len, license,
kernel_version(attempt), log_level, log_buf, log_buf_size);
if (progfd_ >= 0)
diff --git a/src/map.cpp b/src/map.cpp
index 5cfd442..6a452b4 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -46,7 +46,7 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key, int
int value_size = type.size;
int flags = 0;
- mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
+ mapfd_ = bcc_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
if (mapfd_ < 0)
{
std::cerr << "Error creating map: '" << name_ << "'" << std::endl;
@@ -80,7 +80,7 @@ Map::Map(enum bpf_map_type map_type)
std::cerr << "invalid map type" << std::endl;
abort();
}
- mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
+ mapfd_ = bcc_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
if (mapfd_ < 0)
{

安装后的tools路径为”/usr/local/share/bpftrace/tools”,运行如下命令验证安装结果:

bpftrace -e ‘kprobe:do_nanosleep { printf(“PID %d sleeping…\n”, pid); }’

写在最后

  • 本文介绍的方法适用于系统开发阶段,因为有了debian,所以只要能找到源或者代码,几乎可以执行任何linux 发行版上的工具。
  • 后面会陆续介绍其他比较重要的工具(Perf,glances,pidstat,stress等),目前计划还是基于debian的方案。
  • 用户固件中不可能会有这套debian的程序,因为他需要root运行,这是最大的缺点。个人比较认同的方案是4,也就是实现适合用于android 环境的类似BCC&BPFTrace工具链,目标是用户固件中也可指直接使用eBPF。

Reference

  1. “eBPF super powers on ARM64 and Android.pdf” by Joel Fernandes
  2. https://github.com/joelagnel/adeb/blob/master/README.md
  3. https://github.com/joelagnel/adeb/blob/master/BCC.md
  4. https://github.com/iovisor/bcc#tools

eBPF架构优势及其应用方向上的畅想

Posted on 2019-01-28

eBPF架构的优势

本文假设读者已了解以下内容:

  1. 了解BPF/eBPF是什么,了解BPF的演变历史,可参考引用7。
  2. 了解程序的编译与执行流程,虚拟机工作原理。
  3. 大致了解Android系统架构以及开发流程。
  4. 本文是基于嵌入式Linux的开发角度阐述eBPF的应用,对负载情况及需求不一样的其他应用领域(如,云计划,后端服务器)可能不太适用,请读者注意区分。

eBPF功能的概括描述

概括讲,eBPF是一套调试框架,它允许当用户关心的事件发生时允许直接运行用户编写的代码。是不是感觉很熟悉?在Linux中早已存在的Ftrace,SystemTap,甚至Java中ASM技术都有类似的功能。在内核空间中执行到目标函数入口时执行用户事先定义好的代码,可以处理目标函数的入参数据。当目标函数执行完成后返回时也可以执行用户事先定义好的代码,对函数返回值做处理。

注意:eBPF可以通过USDT(User Statically-Defined Tracing)技术对用户空间程序做同样的效果,但因为本质上都是执行在内核空间所以不做过多区分。

对目标函数的处理流程,相比其他Profile工具而言没有特殊之处,抽象此流程为如下:

但是在此执行流程基础上,eBPF的特殊之处在于:

  1. 用户定义的回调函数及数据处理可以直接运行在内核空间。
  2. 用户的回调函数实现是动态执行的,也就是不参与内核或系统的编译,像Shell脚本一样即写即可得。
  3. 大一统了Linux目前主流的调试框架、如:Ftrace,Kprobe,Perf,USDT(User Statically-Defined Tracing)等。

这三个特殊之处可了不得,它使的eBPF相比之前调试工具:

  1. 目标函数数据的处理都执行在内核空间,这就省去了大量的用户与内核空间上的数据拷贝,系统调用,上下文切换等负载。性能强悍到甚至可以直接处理网络请求包(BPF是Berkeley Packet Filters的缩写而e代表Extended),从他的名字就能猜到它就是为此而生。
  2. 因为用户回调函数是动态执行,大大提高了应用上的灵活性。遇到手头问题的时候,想查哪个状态随手一敲就能得到结果。
  3. 之前学习各类Linux上的调试工具时候总觉得东西好多而且好乱。eBPF使用了一套API统一了各种底层调试机制,套用互联网应用开发常说的话,”让程序员更专注在具体业务上”。

要是我的话该怎么设计?

与eBPF类似的调试系统就是Dtrace,它主要应用在Solaris,MacOS上。Linux社区上也有多个分支版本,但始终没有合并到主干上。第一次初步了解eBPF的时候我也设想过如果要我设计类Dtrace的系统,应该怎么设计。假设我们将过程分为三步:

  1. 事件发生时通知到调试系统
  2. 调试系统接到通知后在内核层执行用户注册的回调函数
  3. 用户代码不需要编译,可直接运行

1. 事件发生时通知到调试系统

首先想到就是复用ftrace接口,将原先访问/sys/kernel/debug/tracing操作接口转换成更为方便的API,可供用户层的回调函数使用。后来想到既然都是运行在内核层,不如把范围扩大一些。只要是带符号的内核函数,都可以当做目标函数供用户使用。对目标函数的寻址方法沿用类似Dtrace方案,也就是约定好命名规则。

2. 调试系统接到通知后在内核层执行用户注册的回调函数

既然是可执行代码,那就需要编译。编译器将用户的回调函数直接编译成目标CPU架构的ELF文件后将ELF文件写入到内核的类似ioctl之类的系统调用上。这套理论上可行,但实际上问题很多。有一个难搞定的问题是用户函数可以通过指针操作,访问到内核的任何数据接口,这在安全性上是不可接受的。后来又看到Lua引擎运行在内核上的项目,是不是也可以参考此类架构,在Kernel中安插一个解释脚本引擎,如Lua,甚至简单版本的Java,Python之类。这个方案的不足之处在于,这类语言的编译器比较庞大,对内核来说一是代码不够统一,二是过于复杂容易造成安全漏洞。

3. 用户代码不需要编译,可直接运行

这条与第2条是本质上是同一个,为了支持动态执行,要么写入到内核之前编译好目标代码,要么由内核来直接解释执行。

社区的实现方案

社区的实现相比我的初步设想优点不一样,而且更好更强大。

  • 首先,它统一了几乎所有内核现有的调试框架,ftrace,kproble都可以使用,甚至perf 事件,用户空间程序自定义事件也都没问题。编程接口上使用简单地规则定义了具体要监听哪种调试框架下的哪种事件,使用起来非常方便。
  • 然后参照Java语言虚拟机方案,如下:
    • 先对Java语言通过编译器编译成优化好的Bytecode。
    • 虚拟机对输入进来的Bytecode做例行检查后以解释执行或JIT执行。
  • 社区的方案是将Java语言替换成BPF语言,Java编译器替换成LLVM编译器,它生成出来的文件称为 BPF Bytecode。
  • 使用者把生成出来的Bytecode通过一个叫bpf()系统调用传递给内核,由内核虚拟机处理。
  • 内核没有沿用任何已有的虚拟机,而是实现了一套简单,但是又能保证安全的虚拟机来执行BPF bytecode。它既不能大量的循环操作,也不能访问指定范围外的内核数据。这么做是为了保证内核安全以及回调程序的性能。因为它是执行在内核上下文,如果随便来个for(;;) {}岂不是分分钟把系统搞挂? 这是绝对不允许的。格外需要说明的是BPF程序实际上可以执行循环语句,但数量有限。

有意思的eBPF工具栈

经过上面讨论可知,我们需要用户空间运行的LLVM编译器,也需要跟内核打交道的处理程序(调用bpf()及相关命令),又要把eBPF返回的数据展示给用户。整个流程涉及的点比较多,可以手动编写代码来完成也可以使用社区正在开发的开源项目,BPFtrace及BCC。
如果想要了解eBPF整个流程的话,从简单地demo代码开始入手确是个好选择。请参考引用3项目,学习下最基础的eBPF流程是怎样的。BPFtrace,BCC其实都是这个demo程序的高阶版本,他们存在的目的无非是再封装一层,屏蔽了大量繁琐的细节之后让”让程序员更专注在具体业务上”。

BPFTrace

来几段所谓One liner的程序观赏一下(项目参考引用4):

1
2
3
4
5
6
7
8
9
# bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
Attaching 320 probes...
^C
...
@[tracepoint:syscalls:sys_enter_access]: 3291
@[tracepoint:syscalls:sys_enter_close]: 3897
@[tracepoint:syscalls:sys_enter_newstat]: 4268
@[tracepoint:syscalls:sys_enter_open]: 4609
@[tracepoint:syscalls:sys_enter_mmap]: 4781
1
2
3
4
5
6
7
8
# bpftrace -e 'kprobe:do_sys_open { printf("%s: %s\n", comm, str(arg1)) }'
Attaching 1 probe...
git: .git/objects/da
git: .git/objects/pack
git: /etc/localtime
systemd-journal: /var/log/journal/72d0774c88dc4943ae3d34ac356125dd
DNS Res~ver #15: /etc/hosts
^C

是不是特别简单优美,即使没学过BPF语法一看就能懂。具体细节请参考BPFtrace项目文档,文档写的很全很详细。BPFTrace设计意图在于提供简单地编程方式实现对系统的调试。BPFTrace可以这么简洁的原因是他已经封装好了常用的函数以及程序行为,简单的代价是功能上有所割舍。适用于快速概念验证,调试探索阶段使用,更复杂功能还要看BCC。

BCC(BPF Compiler Collection)

1
2
3
4
5
6
7
8
9
10
11
12
# ./bitehist.py
Tracing... Hit Ctrl-C to end.
^C
kbytes : count distribution
0 -> 1 : 3 | |
2 -> 3 : 0 | |
4 -> 7 : 211 |********** |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 1 | |
128 -> 255 : 800 |**************************************|

BCC的功能基本上BPFTrace类似,但是他呈现的方式不太一样(项目参考引用5),具体表现为:

  1. BCC中的工具虽然提供的功能跟BPFTrace一样,但是他能做更多的事情。不像BPFTrace简单一行代码,它是用Python代码实现的命令,在此程序中加载一个叫BCC的python程序包,在这个库中实现了对eBPF的所有封装。所有它可以利用Python以及庞大的工具库实现非常丰富的监控流程控制与结果展示,如:画图,统计,甚至对输出数据做机器学习训练。
  2. 在Python程序中需要以BPF语言实现回调函数,所以对eBPF行为的控制更佳细致及深入,具体参考Reference Guide(参考应用6)。
  3. 对eBPF返回的结果做更多的处理操作。

总之如作者所说,简单需求使用BPFTrace,复杂需求使用BCC。

eBPF在Android系统优化上的畅想

BCC项目中提供了大量的调试小程序,在我看来这些其实是对历史性能调试工具的重新实现,是以更优雅的方式实现。eBPF更大的魅力在于他的高性能跟灵活性。灵活性体现在只要是编译好的bytecode内核可以直接执行,这在线上固件的调试上带来了极大的便利性。

强悍的性能

实现直接对用户版本的固件进行性能回归测试。之前为了达到同样的效果,要么直接对内核修改,要么开启大量调试功能并执行特殊的性能分析程序。 这会带来的一个麻烦问题是需要维护单独开发分支以免干扰用户主干分支,而且随着时间的迭代,代码基线会偏离于线上用户版本,后续维护成本很大。所以在CI/CD的落地过程中,系统固件部分的测试是工程实践难点。如果eBPF的高性能可以满足可接受范围内的性能损失,那完全可以直接拿线上版本做回归测试。

虚拟机带来的灵活性

实现针对线上用户的单点下发,以了解更多的用户实际使用的系统负载情况。了解真实用户的系统负载对改进系统设计来说帮助很大,他就像一个灯塔指示了优化方向及系统瓶颈点。但痛点是在用户版本固件中不敢开大量的调试功能用于收集系统运行状态,因为性能损耗非常大。在eBPF之后可以根据情况随时修改脚本,并做灰度测试用于收集相关运行指标,会大大加快整个优化流程。Android中的很多应用都热衷于热修复一样,不打扰用户的情况下修改线上程序的需求在系统开发时也是很有用的功能。

写在最后

  1. 随着软件技术的发展,调试机制也会不断发展,方向是朝着更具有框架性,更易于使用,更高性能为目标。不像之前那样,为了解决特定问题而设计,在完整性上提升了很多。
  2. 业务负载变复杂的同时,手上工具也需要越来越灵活多变,用以面对不停变化的局面。先进生产力,有一部分是体现在工具的使用上,好的工具可避免没必要的时间浪费,提升工作效率,将精力集中在更有挑战的事情上。
  3. 随说软件轮子复用是好工程的指标之一,但从eBPF实现来看它借鉴了其他项目的好想法但实现上完全是另起炉灶。
  4. 软甲架构设计中,分层思路是万古不变的原则。
  5. BCC在android上的应用实践请参考本博的博文eBPF on Android

Reference

  1. http://www.brendangregg.com/perf.html
  2. https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#probes
  3. https://github.com/pratyushanand/learn-bpf
  4. https://github.com/iovisor/bpftrace
  5. https://github.com/iovisor/bcc
  6. https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
  7. https://www.ibm.com/developerworks/cn/linux/l-lo-eBPF-history/index.html ; eBPF 简史
  8. http://www.caveman.work/2019/01/29/eBPF-on-Android/

以Little's Law角度解读iostat输出的avgqu-sz(平均队列长度)

Posted on 2018-11-13

Last update: 20181213

之前在博文性能分析中科特尔法则(Little’s Law)与其衍生法则的应用有介绍过Little’s law合它的简单证明过程,但此文中并没有给出相应的应用实例。 本文通过以Little’s law角度解读iostat命令输出的avgqu-sz指标。

查看平均队列长度

最简单的方法是通过命令 iostat -x 1 以每隔一秒一次输出队列长度,如下图所示。

红框所示的avgqu-sz就是平均队列长度了,这里直接引用博文 http://bean-li.github.io/dive-into-iostat/
中给出的计算方法。

我们换一种思路来考虑,即diskstats中time_in_queue的思路。
当第一个IO完成的时候,队列中250个IO,250个IO都等了4ms,即time_in_queue + = (2504) ,当第二个IO完成的时候,time_in_queue += (2494),当所有IO都完成的时候,time_in_queue = 4*(250+249+248….+1), …
根据time_in_queue/1000,殊途同归地获得了平均队列长度。

第一次读到这段解释时似懂非懂,后来想起来在证明Little’s Law的过程中曾使用面积的来替代队列中的总等待时间并除以观察时间得出平均队列长度。通过这个方法来理解iostat的计算队列长度会简单得多,而且有利于加深对Little’s Law的理解。

借用Little’s Law的证明思路

我们回顾下Little’s law的证明过程,

Little’s law中不太好理解的参数是队列中平均任务数,请参考上图中的绿色部分A(T)。A(T)为T观察时间范围内,已经入队到队列中的所有任务等待时间的总和,也可以理解为绿色部分的面积。当由n(t)导致的不规则绿色区域抹平成长方形时,设T为长方形的宽,队列中的平均任务数为高,则A(T) = T * 队列中平均任务数。设L(T) = A(T) / T,则L(T)等同于平均等待任务数(也称为平均队列长度)。

上面一段是博文”性能分析中科特尔法则(Little’s Law)与其衍生法则的应用”的片段。由于观察范围时间T可以由我们指定,那我们需要计算出绿色区域面积就可以得出平均队列长度。

那问题变成了我们怎么能获取队列中的所有任务的等待时间呢?
在Linux 内核(以4.4版本为例)中有如下代码,它在每次关键IO事件时会被调用。

其中time_in_queue就等同于上面所说的”入队到队列中的所有任务等待时间的总和”。
每当发生以下几个IO事件时会触发IO统计,也就是调用part_round_stats_single()。

  • 当有新的IO请求入队
  • 当IO请求被已有请求合并(Front merge or Back merge)时
  • 当完成某个IO请求时

在博文 http://bean-li.github.io/dive-into-iostat/中有提到Merge时不会做IO统计函数,但是在kernel 4.4中可以到由blk_queue_bio()函数根据情况分别会调用attempt_front_merge()或attempt_back_merge()。

每当发生关键IO事件;发起请求,完成请求,请求被合并时查看当前有几条正在执行的读请求IO或写请求IO,将此值乘以临近时间差就得出了系统当前处理IO时的总耗时。这与Little’ Law中通过面积来计算总耗时是一个道理,”绿色面积”中每当一个请求来的时候就上调一格,完成IO时就下调一格。在观察时间周期T内,以此方法来画出的绿色区域就是请求的总耗时时间。需要再次强调的关键概念是:

  • 系统同时可以有多个读请求
  • 系统同时可以有多个写请求
  • 读请求与写请求可以同时发生

2:内核函数part_in_flight()中返回的IO数目也是读和写请求的总和。

如何获取平均服务时间?

Linux的/proc/diskstat中目前(4.4版本)只提供了处理完成的IO请求数量与IO等待时间,并没有提供请求入队的IO请求数量。在”性能分析中科特尔法则(Little’s Law)与其衍生法则的应用”也提到过Utilization law,是指当资源使用率不超过100%时可认为抵达率与完成率是一致的,这也意味着当资源使用率未达到100%时可以通过完成率替代抵达率以计算服务平均处理时间。iostat中用的方法也是利用了这一点,但本质上是Utilization law在发挥作用。如果遇到资源使用率达到性能拐点处时就已经不是job flow balance状态了(出现排队)。以严谨起见,最好是能获取到真正的请求入队数量,这样可以套用Little’s law计算出真正的平均服务时间。目前想到的唯一方法是通过修改内核代码以获取入队请求次数。

W = L / λ

补充:

当谈到Disk IO的服务时间时注意区分两种情况:

  • IO队列服务时间 = 磁盘忙于IO请求的时间 + IO请求在队列中等待时间
  • 磁盘IO服务时间 = 磁盘忙于IO请求的时间

Little’s Law给出的平均服务时间是指IO队列服务时间,他是包括了队列中的等待时间。那实践中以哪个指标为主呢?我觉得是根据应用场景不同而不同,

  • 分析某个workload characterization 时会侧重看IO队列服务时间
  • 分析某个设备整体情况时会侧重看磁盘IO服务时间。基于Flash的存储设备出现严重的文件碎片化时此值会暴增。

参考

  • http://bean-li.github.io/dive-into-iostat/
  • 性能分析中科特尔法则(Little’s Law)与其衍生法则的应用

Android UX performance check list

Posted on 2018-10-06

Last update 20181006

UX性能检查清单

Android设备的用户体验性能包括:

  • 界面流畅度优化
    • 游戏类应用
    • 普通用户交互类应用 如:社交APP,购物APP
  • 界面响应速度
  • 更多场景待补充

本文通过系统化的检查清单,以流程化的方式排查UX性能问题。随着对系统认识的加深与改变,清单内容也会跟着改变。

首先,我们将Android系统以如下方式划分:

将整个栈分为5层,4层软件+1层硬件。每一层由排查方向(Orientation)与排查工具(Diagnose Tools)组成。检查清单的作用在于以流程化的方式避免了不必要的时间浪费,清单方法看起来比较慢(或者”笨”),但在实际中它是最快,最完整的问题解决方法。所以本文注意力将放在清单的完备性上,与具体模块相关的内容(排查方向与工具使用)将在后续的文章中做详细介绍。

清单说明:

  • Application与Frameworks是运行在应用程序上下文中。
  • Core Services是指Android关键服务,它配合LinuxKernel支持Android应用程序的运行。
  • 检查顺序是从层1 Application 开始,按编号顺序检查。

[层1 Application]

[层2 Frameworks]

[层3 Core services]

[层4 Linux kernel]

[层5 Chips]

TimeSeries关联性分析的应用

Posted on 2018-08-17

Last update: 20180828

什么是TimeSeries关联性分析(Correlation Analysis)

假设有基于时间序列采集的两组同样大小的数据,关联性分析是指量化这两组数据间的关联程度。再次强调一下,本文中讨论的关联性分析是针对TimeSeries数据类型的,在自然语言处理中用到的关联性分析方法是基于信息熵,与文本中讨论的方法不相同,虽然他们都属于关联性分析。

  • 如果数据A上涨时,数据B上涨(同样适应于下跌的情况)则说明这两组数据有关联性,表示为正向关联。
  • 如果数据A上涨时,数据B下跌则说明这两组数据有关联性,表示为反向关联。
  • 关联程度取决于两组数据间的变化幅度。

为什么要做关联性分析

包括但不限于:

  • 使用聚类算法定位性能瓶颈。
  • 结合数据可视化,进行信息挖掘。
  • 根因分析。

实践中比较实用的用法是分析某个指标的变动会引起哪类其他指标的变动。比如可以回答如下问题:

  • Memory Cached与Memory Free间是什么关系?关联程度如何?
  • IO WriteBack频繁程度与哪种指标有关联?

在设计系统资源调度策略与参数配置上,这类信息有助于系统最优设计。当优化某个关键指标时,需要查看与其关联的其他指标以确保不会出现指标失衡情况(改善一个指标时导致另一个指标的恶化)。通过此方法还可以分析出设备的硬件配置在运行用户负载程序时它的主要瓶颈是什么,针对不同资源瓶颈,配置不同的资源调度参数以实现能效的最优化。

常用算法之 - Pearson Correlation Coefficient

公式 - 来自Wikipedia

  • Python Code: Scipy.stats.pearson()
  • 取值范围: [-1.0 ~ 1.0]

使用Pearson时的注意点如下:

  • 函数结果的绝对值越接近1.0则关联性越强,越趋近于0.0则表明没有关联性。
  • 两组数据间需要有独立性。
  • 样本间需要有线性关系。

常用算法之 - Spearman Rank Correlation Coefficient

公式 - 来自Wikipedia

  • Python Code: Scipy.stats.spearmanr()
  • 取值范围: [-1.0 ~ 1.0]

使用Spearman时的注意点如下:

  • 函数结果的绝对值越接近1.0则关联性越强,越趋近于0.0则表明没有关联性。
  • 两组数据间需要有独立性。
  • 样本间不止线性关系,满足单调关系时也适用。

不过从实际表现来看,Pearson与Spearman不需要严格的遵从关系函数(线性,单调)。

常用算法之 - Normalized Cross-Correlation

公式 - 来自Anomaly.io

  • 函数结果的绝对值越接近1.0则关联性越强,越趋近于0.0则表明没有关联性。
  • 抗异常值的干扰能力较强,这也意味着肉眼上看不是很明显的关联关系使用NCC计算时得分是比较高的。
  • NCC适合量化两组数据间的数值上的浮动程度(波动)。

从PCC与SCC的结果上看两组数据没有明显的关联性,但从NCC上看是有较强关系的。从肉眼上分析,两组数据间有较强的”贴合”关系,但不具备明显的关联性关系。

两种关系

将不同指标间的关联性属性划分为:

  • 直接关系(Direct Correlation)
  • 间接关系(Indirect Correlation)

直接关系是指两个metric间有正向关联或反向关联,总之是有某种的直接关联。间接关系是指两个metric间通过某种逻辑关系关联在一起且数值的变化可能不是实时的,会有一段时间的延迟。需要特殊说明的是,严格意义上来说在一个系统中的任意两个指标都会有关联关系,区别在于关联关系强弱与时效性。

案例分析

Memory Free与Memory Cached间的对比

  • NCC较高但Pearson与Spearman给出的低分来看,这两个指标在这段时间内属于间接关联。
  • 从free与cached回收原理上看,两个虽然有关系但并不是直接关系。这个依赖具体的PageCache 与Memory Reclaim算法。

Memory Buffers与Memory Cached间的对比

  • Pearson与Spearman都给出0.5以上的分,说明两者有一些直接关系,只是强度不是很大。
  • 从buffers与cached回收原理上看,两者其实都算是PageCache缓存。当遇到内存吃紧,IO 回写情景时两个buffer都会受到影响。

Memory Free与 UX FrameDrop间的对比

  • Pearson与Spearman给出的分数来看,两个指标属于间接关系。
  • 从原理上看低内存有可能会引起前台UX应用的卡顿,但这两者间并不具备直接关系。

这里只给出了某个设备在某个时段的信息,而且还只是某个特定UX应用的关联性分析。在实际中,不同UX应用针对不同的metric间的关联性是不同的。比如有些应用是Memory sensitive,而有些是IO sensitive。只有经过大量数据(不同时段)的计算后才能给出较为准确的结论。

其他常见的关联性分析算法,根据特性适用于不同领域。

  • Apriori/FP Growth(Tree): 经典的超市关联分析中用到的算法。在多个异常指标的聚类时,也可以用此算法做初步筛查。
  • Canonical Correlation Analysis
  • Maximum Information Coeffcient
  • Kendall Correlation Coefficient
  • Euclidean distance
  • Mutual Information

写在最后

  • 这个世界很复杂,无法用一个指标或者量化方法理解所有现象。
  • 使用指标时应根据现实数据情况采用不同的量化指标。
  • 先采用数据可视化的方式观察下数据间的大致关系,之后根据其结果选择合适的量化指标。
  • 数据可视化工具中可以同时展示关联性指标的计算结果,有助于数据解读。

Reference

  • https://en.wikipedia.org/wiki/Pearson_correlation_coefficient
  • https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient
  • https://anomaly.io/understand-auto-cross-correlation-normalized-shift/

应用程序界面流畅度的量化方法与应用

Posted on 2018-07-25

Last update: 20180725

指标与现实不匹配

应用流畅度是我们最关心的性能优化指标之一,本文主要介绍流畅度指标的理解与实际应用。
流畅度评判的难度在于量化指标并不能完全对应到感官体验上。

这什么意思呢? 举个简单例子;

假设有一个过场动画,从FPS,FrameUpdate等指标来看都是满分的,也就是FPS = 60, FrameUpdate <= 16ms。
但从观感来看动画不是”够”流畅,过程中有顿挫感。更有意思的是,因为它是主观感受,所以其结论也是因人而异。

优化效果的验证方法

验证优化效果的流程可拆分为如下:

  • Code Build:编译出来的程序
  • Verify in Lab:由实验室环境下由人使用测试用例验证
  • CI by Robot:程序由可持续集成框架(CI: Continuous Integration)进行自动化性能测试
  • Monitor in RUM:使用大数据工具分析线上用户数据在真实环境下的数据

我们推荐的做法是:

  • 将定性方法(Qualitative method)应用在 [Verify in Lab]。
  • 将定量方法(Quantitative method)应用在 [CI by Robot],[Monitor in RUM]。

定性方法 (Qualitative method)

由于有些人对应用流畅度感受不够敏感,可以由对其比较敏感,在意的人员来操作。流畅度测试与其他测试不同,他需要
测试人员的对卡顿现象(界面更新不自然)的有与生俱来的感觉与挑剔。 拥有这种品质的人是定性测试的最佳人选。

定量方法 (Quantitative method)

针对流畅度量化方法,我们的解决方案是不同界面场景使用不同的量化指标。

常见应用场景:

  • 流媒体,视频播放,相机类
  • 游戏类
  • UX应用-内容浏览类
  • UX应用-内容交互类

流畅度量化指标:

  • FPS Family
    • Realtime FPS
    • Static FPS
    • FPS Stability
  • Frame Drop Family
    • Frame Drop Percent (FDP)
    • Frame Drop Badass (FDB)
    • Frame Drop Heavy Tail (FDHT)
  • Heavy task in UI Thread (HT)

应用场景与量化指标间的使用关系为:

  • [流媒体,视频播放,相机类} -> [FPS Familiy]
  • [游戏类] -> [FPS Family]
  • [UX应用-内容浏览类] -> [FDP, FDB, HT]
  • [UX应用-内容交互类] -> [FDHT, HT]

待办项:

  • 量化指标三大类具体计算方法?
  • 场景与量化方法间匹配原则?

Reference

  • Web Performance: Leveraging the Metrics that Most Affect User Experience (Google I/O ‘17)

通过Responsive Time Law理解性能拐点(Knee Of The Curve)

Posted on 2018-07-01

Last update: 20180703

性能分析中除了Little’s law之外 Responsive time law(以下简称为RTL) 也是常用到的公式。RTL的最大贡献是量化了资源使用率与响应时间间的关系。性能分析领域中有个著名的点,叫”性能拐点(Knee of the cureve)”,本文讨论我关于这个点的几个思考。通过RTL公式可以看到当资源使用率超过某个点时响应时间会暴涨,这会引申出三个思考点:

  • RTL公式究竟长成什么样?
  • 性能拐点的定义是什么?
  • 性能拐点的应用

RTL公式究竟长成什么样?

RTL公式是通过Erlang C推导而来的,而Erlang C是用于描述排队论中任务被阻塞等待的概率。RTL公式目前只适用于符合M/M/c排队模型的队列。M/M/c是什么意思呢?他是Kendal notaion,具体意义为:

  • M:请求到达的时间间隔呈现为指数分布,平均到达请求数量呈泊松分布。
  • M:服务时间分布呈指数分布。
  • c:服务数量 1为队列中只有一个server,2为有两个server。

当队列满足M/M/c模型时计算响应时间公式为:

响应时间 = 队列等待时间 + 服务时间
函数参数 c为server数量,p为资源使用率。

使用Erlang C函数展开队列等待时间为:

这里给出的是最终的结果,使用ErlangC推导整个等待队列时间比较复杂故此省略了。
Erlang在1917年通过泊松概率分布推导出了Erlang C公式,用于量化当时电信业务。需要注意的是使用Erlang C计算RTL时服务请求与服务处理时间需要满足各自的概率分布,也就是指数分布与泊松分布,而且要求每个请求间是相互独立,互不依赖。

另外值得一提的是网上还有一种简化版的RTL公式:

m 为服务数量,与上一个公式中的c相同。

这个公式在m满足<=2时与使用Erlang C的版本在结果上是一致的,但是当m超过2时精度没有比使用Erlang版本的高。简化版公式的意义在于可以通过心算的方式大致估摸出结果,再者就是减少计算量。但为了准确性,在这里还是推荐Erlang版本(毕竟这点计算量在现代处理器上是不叫事儿了)。

当c = 8,绿色为Erlang版本,紫色为简化版,从肉眼上可以看到简化版比较消极一些。从严谨的意义来说,这会使我们误认为系统性能相比实际情况好一些。

性能拐点的定义是什么?

从上面曲线中可以看出越是接近1,响应时间(y轴)上升坡度越陡峭。曲线中存在某个点(资源使用率),超过这个点时响应时间会暴涨,我们称呼他为Knee of the curve。那Knee的具体定义是什么样的呢?我查阅相关资料时发现有多种不同的定义;

  1. 使用率达到75%的点为Knee。
  2. 有一条直线,穿过原点并与曲线交接的点为Knee。
  3. 响应时间为服务时间的2倍的点为Knee。

可以明显的看出定义1是不够准确的,因为从曲线中看出随着server的数量(c)的增大曲线的陡峭是越往后移的。如果设75%为Knee点,在多Server的队列中会浪费硬件资源。定义2是从数学角度得出的Knee,这在实际业务中的实际应用效果有待观察。定义3是根据业务的需求得出的定义,比如有些业务严格要求响应时间不能超过服务时间的两倍,但有些又可以容忍到三倍。

所以总结来看,RTL曲线虽然可以提示我们随着使用量的增大,响应时间以类似曲棍球杆的形状陡峭增长,但具体从哪个Knee点开始性能变差且不可接受是没有标准答案的。因为,它依赖具体的业务,依赖你的客户容忍度,依赖你的硬件成本,依赖你的软件架构。

读者可以通过点此曲线来观察下不同的c与S变量下曲线的变化程度。

性能拐点的应用

既然无法得出具体的Knee点定义,那我们怎么利用好它呢?通过以下两个角度来思考

  • 资源使用率
  • 响应时间的容忍值

资源使用率的思考:

  • 业务是批处理时:可以100%资源使用,因为此时尽可能榨干硬件性能是有利于资源利用。
  • 业务是与用户的交互场景时:资源率使用不宜超过Knee,当然这时根据业务需要定义Knee点。
  • 业务是批处理与用户交互场景的混合型时:
    • 区分出前台与后台任务
    • 通过Resource Control方法设置前后台各自的资源使用率上限 e.g. cpuset, cgroup
    • 当用户交互时提高前台资源的预留比例并限制后台任务请求。

响应时间的容忍值的思考:

  • 通过降低服务时间以尽可能降低响应时间
  • 控制任务抵达率;从Utilization Law可知当任务抵达率提高时资源使用率也会提高,参考: 性能分析中科特尔法则(Little’s Law)与其衍生法则的应用。
  • 项目规划时把Performance定义到Feature级别,并根据业务情况定义响应时间的可接受范围,参考: 使用置信区间量化应用程序启动时间。
  • Knee点根据响应时间可接受范围来定义,然后通过减少服务时间,控制资源使用率来满足业务要求。

写在最后

  • 概率统计是一门非常有意思的学科,将其利用到性能数据分析上是未来学习方向之一。
  • 除了数据可视化之外,函数可视化也能提供不少线索。本文中使用的曲线由desmos.com生成,推荐给各位。
  • 性能分析时不要相信自己的直觉,一定要通过[数据收集 -> 建模 -> 量化结果]的方法来验证效果。

Reference

  • https://en.wikipedia.org/wiki/M/M/c_queue
  • https://en.wikipedia.org/wiki/Erlang_(unit)#Erlang_C_formula
  • https://www.xaprb.com/blog/response-time-stretch-factor/
  • http://blog.sina.com.cn/s/blog_5fe911250100dv3v.html
  • http://www.mitan.co.uk/erlang/elgcspsh.htm
  • Forecasting Oracle Performane by Craig Shallahamer ISBN-10: 1-59059-802-4
  • Optimizing-Oracle-Performance by Cary Millsap, Jeff Holt ISBN-10: 059600527X

性能分析中科特尔法则(Little's Law)与其衍生法则的应用

Posted on 2018-05-20

Last update: 20181112

科特尔法则 Little’s Law

科特尔法则(Little’s Law)是John Little教授在1961证明并以他的名字命名的公式。Little’s Law是排队论中的重要公式,因现实世界中排队模型的应用非常广泛这也意味着此公式的应用也非常广泛。在性能分析中我们通过Little’s Law定位性能瓶颈,用模型预测随负载增加对业务性能的影响,以及通过容量规划提前为业务扩张做准备。在现实生活中凡是排队等待的情况都可以通过Little’s Law进行量化与优化(如银行,医院的排队取号)。

Little’s Law通过一个简单的公式量化了吞吐率与服务时间及等待队列长度三者之间的关系,简单公式的背后隐藏着巨大的信息量。
本文以最简单的排队模型介绍Little’s Law,如下图所示:

假设存在一个系统由单输入与单输出单元组成并且只有一个处理单元。当输入到系统中的请求速度快于处理单元处理速度时系统会出现排队等待情况。

Little’s Law 公式

$$L= λ * W$$

队列中平均任务数 = 平均任务抵达率 * 平均任务处理时间(含队列等待时间)

Little’s law中不太好理解的参数是队列中平均任务数,请参考上图中的绿色部分A(T)。A(T)为T观察时间范围内,已经入队到队列中的所有任务等待时间的总和,也可以理解为绿色部分的面积。当由n(t)导致的不规则绿色区域抹平成长方形时,设T为长方形的宽,队列中的平均任务数为高,则A(T) = T * 队列中平均任务数。设L(T) = A(T) / T,则L(T)等同于平均等待任务数(也称为平均队列长度)。

公式注解:

  • Little’s law的应用场景非常广泛,它既可以解释单一的系统部件(磁盘,CPU)也可以解释由多个子系统组成的复杂系统(网页界面响应时间)。
  • 当平均队列长度变大时请求抵达率也会随着变大。只有系统待要处理的任务变多时才有可能使系统以最高吞吐率模式工作并榨干硬件性能,但他的反面是系统中入队等待任务变多,如果这个任务是跟用户体验相关时会表现为操作响应时间的变长(参见下面的响应时间部分内容)。
  • 系统处理单元的平均服务时间变长时会导致等待队列的变长,优化等待队列长度的方向有:
    • 缩短平均服务时间
    • 减少任务请求量
  • 本文介绍的是单一处理模型,如涉及多个并行处理模型时优化方法不止上面两种。

Little’s Law 衍生应用 - Utilization Law 使用率的计算

Utilization Law 公式

$$U = X * St$$

使用率 = 吞吐率 * 服务处理时间

当使用率不超过100%时Utlization law其实是little’s Law的一个变种,这种情况被称为Job flow balance(此时抵达率与吞吐率相同)。

公式注解:

  • 吞吐率不变的情况下服务处理时间的提升导致使用率的提升。
  • 在使用率不变的前提下要使吞吐率上升需要缩短服务处理时间。

在磁盘性能老化分析时有时候会遇到磁盘使用率变化不大但磁盘吞吐率反而降低了。这是因为老化(磁盘长时间使用后)后的磁盘可能会出现文件碎片化。在读取以碎片化形式存储在磁盘各个地方的文件时可能会加长磁盘寻道时间(物理磁头转动时间),这个操作会体现在磁盘服务处理时间上,通过公式可知此时磁盘使用率变化不大但因处理服务时间变长导致了吞吐率的下降。

写在最后

  • 性能优化属于工程领域,工程领域就需要通过量化方法评判优化结果的好与坏。
  • 在数据分析时除了通过简单美妙的公式描述关系之外还可以通过数据可视化方法得出”数据模式”上的新发现。人的大脑善于从图形化内容中找出相互间的关联性,这个优点也可以用于数据分析。
  • 通过现象看到本质,这应该是我们平时要锻炼的思维模式。 将本质转变成高度抽象的公式是不错的方法之一。

参考

  • http://web.mit.edu/~sgraves/www/papers/Little%27s%20Law-Published.pdf //John D.C. Little 发表在MIT刊物上关于Little’s law的论文
  • https://en.wikipedia.org/wiki/John_Little_(academic) // John Little 介绍
12

Caveman.Work

Life is fantanstic!

12 posts
© 2019 Caveman.Work