背景

工作中常有些这样需求,需要分析zip解压缩,androbench和应用安装等存储应用场景在不同版本,不同平台的性能差异,并且需要对这些场景做性能优化。

想要做好这些需求工作,首先得对这些场景下的IO特点有所熟悉,知道这些场景会给手机施加什么样的workload。

这一步做好后,对下来无论是调研存储性能优化方案,还是分析这些场景对应的性能jira都会有很大帮助的。

IO workload解析

解析工具介绍

由于部分app代码不开源(比如androbench等),并且就算代码开源,由于上层代码逻辑复杂,要想对手机众多使用场景下的io workload做很好地解析,

最好开发出一个统一的解析方案,可以对不同io workload进行解析。

这边经过一番调研,发现结合着使用如下两个工具可以很好地做到解析工作。

自己定制的collect-straces.sh脚本

      该脚本借助于Linux平台上强大的strace工具,可以对指定进程运行时的各种syscall 调用(目前只限于IO方面)进行trace跟踪记录,

 并且也可以自动对跟踪进程派发出来的子线程进行syscall跟踪,而且还可以算出来syscall调用耗时,追踪到syscall有无调用失败等。

基于ebpf接口的bcc工具

      上面介绍的是syscall层面的workload跟踪,很多时候还想清楚地知道底层kernel里面文件系统层,block层和存储设备层各个层面的workload状况。

  这样便于对kernel存储模块进行调优。所以这个时候就可以借助bcc工具了。

      该工具集由我这边做了定制和功能强化,里面有几个工具可以做到workload解析。

1:ioworkload_analysis工具

     该工具会卡住vfs_read/vfs_write等函数,可以在文件系统这一层进行跟踪。

2:directio_analysis工具

     该工具卡住__blockdev_direct_IO函数,可以在block这一层进行workload跟踪(目前仅限于ext4文件系统+Androbench场景使用)。

3:biosnoop工具

      该工具会卡住blk_start_request,blk_account_io_completion函数,可以对存储设备层处理IO请求方面进行跟踪。

4:stackcount工具

     该工具的-P选项和-d选项结合起来用,可以看到待trace函数在系统所有进程里面的上级函数调用链状状况(调用链包括用户态函数调用)。

下面是由我解析出来的一些手机应用场景对应的io workload状况。

bcc工具对系统进行trace时,产生的overhead比strace工具要小的多。strace工具跟踪时,产生的overhead比较大,对IO性能会有比较大的影响的。

androbench解析

micro测试项

分析方法

syscall层面,可以用上面说的strace工具。

内核层面,用bcc里面的stackcount工具分析Android系统底层submit bio时对应的上级函数调用链。发现androbench进行存储测试时,submit bio的上级调用函数链必经过vfs_read和vfs_write。

所以用ioworkload_analysis工具卡住vfs_read和vfs_write这两个内核函数,便可以分析到androbench在内核层面具体是怎么进行micro测试的。

解析出的workload状况

androbench测试之前,会顺序buffer write写生成8个文件(文件名依次为0 - 7),文件尺寸都为64M。各个文件生成完后,会分别调用fsync保证文件数据,元数据落盘。

一 顺序读写测试

单线程测试

1:依次direct顺序读上面生成的0-7文件,循环读3轮,每个文件读两次,每次读(对应vfs_read)32M的数据。

2:依次direct顺序写上面生成的0-7文件,循环写3轮,每个文件写两次,每次写(对应vfs_write)32M的数据。

      写是带有O_SYNC flag的,这意味着每次写调用在最后一步都做fsync的工作。

顺序读写产生的总文件数据量为1536MB = 8 × 3 × 64MB,该数据量是固定不变的,每轮测试都一样。

其中的8对应0-7共8个文件,3对应循环测试3轮,

64MB是缺省模式下每个文件的尺寸大小(前面已经提过),因为缺省模式下,顺序读写的IO大小为32M,所以正好每个文件需要顺序读or写2次,才能完成整个文件的IO访问。

顺序读写评测的是手机存储芯片的吞吐量:MB/s.

吞吐量计算公式:1536MB  /  顺序(读 or 写)测试耗时。

(仅在e1 android q上验证是这样的,目前用bcc工具算出的值都是很接近,但是稍微大于androbench app算出的值,还需要多机型再验证)。

二 随机读写测试

多线程测试,会开启8个线程(缺省模式的配置)进行并发IO测试,每个线程对应操作0-7中的一个文件。

具体的IO测试工作如下:

1:先做多线程的随机读测试,每次读IO大小为4kb。

2:后做多线程的随机写测试,每次写IO大小为4kb。

     写也是带有O_SYNC flag的,也会每次写后,做fsync的工作。

随机读写的文件逻辑地址不会超过文件的大小:64M。

随机读写评测的是手机存储芯片的IOPS性能。

IOPS计算公式:随机(读 or 写)测试总共产生的IOPS次数  /  随机(读 or 写)测试耗时。

(仅在e1 android q上验证是这样的,目前用bcc工具算出的值都是很接近,但是稍微小于androbench app算出的值,感觉还有点问题,还需要多机型再验证)。

总结

从micro测试项的workload解析,可以看出来androbench这个存储测试工具设计目标是尽量绕开内核文件系统层(1测试之前fsync待测文件的数据; 2采用direct io方式进行性能测试;3无论顺序读写还是随机读写都是覆盖读写,文件逻辑地址都不会超过64M,避免读写测试时陷入ext4文件系统的磁盘物理块分配这种耗时工作),最大程度上感知手机存储芯片的IO性能。

sqlite测试项

java层面分析

打开sqlite相关调试开关后,看到logcat中SQLiteStatements相关输出:

androbench增删改测试项,都是对testing_sqlite.db文件进行测试的,各自不断循环输出如下内容

sqlite insert:
.........
08-28 23:13:58.562 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "BEGIN EXCLUSIVE;"
08-28 23:13:58.563 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "INSERT INTO friends(position,phonetic_name,phone_number,member_type,contact_id,type,raw_phone_number,ext..........."
08-28 23:13:58.612 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "COMMIT;"
sqlite update:
..........
08-28 23:14:03.692 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "BEGIN EXCLUSIVE;"
08-28 23:14:03.692 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "UPDATE friends SET position=0,phonetic_name=NULL......."
08-28 23:14:03.692 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "COMMIT;"
sqlite delete:
..........
08-28 23:14:05.706 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "BEGIN EXCLUSIVE;"
08-28 23:14:05.706 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "DELETE FROM friends where id=17321;"
08-28 23:14:05.706 14204 14353 V SQLiteStatements: /data/user/0/com.andromeda.androbench2/databases/testing_sqlite.db: "COMMIT;"

内核层面分析

共同之处:

sqlite增删改测试用的是testing_sqlite.db和testing_sqlite.db-wal这两个文件。

具体测试时,都是单线程buffer IO读写,对testing_sqlite.db-wal文件统一写完后,会对该文件执行下fsync(赶好对应sqlite的normal mode)。然后把wal文件内容写回到testing_sqlite.db文件中(对应的正是数据库的checkpoint工作)。

sqlite insert :

数据库的checkpoint的时候,才会有读操作。

sqlite delete:

非checkpoint阶段,也会有读操作。

由于对sqlite的细致工作流程还不熟悉,所以这部分后来有空还可以继续解析。