续上一篇《Android系统性能监控最全面分析与实践(一)》,本文主要分析内存、网速、磁盘读写速度,go on!!
4、内存占用获取
4.1 当前内存使用情况
内存使用情况也是老生常谈的问题了,网上有很多资料,在此简单重温一遍。
方案一:cat /proc/meminfo
/proc/meminfo展示了系统内存使用情况,其中有两个关键指标:
- MemTotal:代表系统总内存大小,单位是KB
- MemFree:代表系统当前可用内存大小,单位是KB 在apk执行cat指令读取该文件可以获取到目标数值,无论是效率还是适用性都很高。
方案二:dumpsys meminfo
dumpsys meminfo + [pkg]通常用来查看指定进程包名的内存使用情况,如果不加包名,可以得到当前内存总量和可使用内存量,其中:
- Total RAM:内存总量
- Free RAM:可使用内存量
然而dumpsys meminfo耗时达到秒级,耗时较久,效率不高。
方案三:free
使用free命令可以查看系统当前内存使用情况,其中有两个关键数据:
- total:总内存大小
- used:已使用的大小
虽然也满足需求,但本方案和方案一和二,都是通过执行shell指令获取一串数据,因此需要对数据加以解析才能得到目标数据,在方案实现上稍微麻烦一点。
方案四:MemoryInfo 获取
ActivityManager服务是对Activity管理、运行时功能管理和运行时数据结构的封装,进程、应用程序、服务、任务信息等。通过 ActivityManager 获取到的 MemoryInfo 是整个设备的内存信息,主要包括:
- totalMem:内核可访问的总内存。
- availMem:系统上的可用内存。
- lowMemory:如果系统认为自己当前处于低内存状况,则设置为True。
- threshold:系统认为内存较低并开始杀死后台服务和其他非外部进程的可用阈值。
val memoryInfo = ActivityManager.MemoryInfo()
mActivityManager.getMemoryInfo(memoryInfo)
totalMemory = memoryInfo.totalMem
availMemroy = memoryInfo.availMem
该方案是针对应用层获取系统内存信息的API函数,在应用层获取内存信息,推荐使用该方案。
结论
| 获取内存使用情况的方法 | 效率 | 实现难度 | 推荐 |
|---|---|---|---|
| dumpsys meminfo | 低 | 中 | |
| cat /proc/meminfo | 高 | 中 | |
| free | 高 | 中 | |
| MemoryInfo 获取 | 高 | 易 | ✔ |
4.2 内存使用率TOP5进程获取
前文获取了实时CPU使用率最高的5个进程,那么实时占用内存最高的5个进程也有必要获取。在获取指标之前,我们先来了解几个名词:
- RSS:(Resident Set Size)实际使用物理内存(包含共享库占用的内存)。RSS可能具有误导性,因为它包括了该进程与其他进程共享的实际物理内存使用量,例如,对于共享库,其往往只加载到内存中一次,而不管有多少进程使用它。因此RSS不能准确地表示单个进程的内存使用情况。
- PSS:(Proportional Set Size)实际使用的物理内存(比例分配共享库占用的内存),比如2个进程使用10KB的共享库,那么每个进程算5KB内存占用到PSS中。这种方式表示进程的内存使用情况较准确,但当只有一个进程使用共享库时,其情况和RSS一模一样。
- VSS:(Virtual Set Size) 虚拟耗用内存(包含共享库占用的内存).表示一个进程可访问的全部内存地址空间的大小。这个大小包括了进程已经申请但尚未使用的内存空间。在实际中很少用这种方式来表示进程占用内存的情况,用它来表示单个进程的内存使用情况是不准确的。
- USS: (Unique Set Size) 进程独自占用的物理内存(不包含共享库占用的内存),表示一个进程本身占用的内存空间大小,不包含其它任何成分,这是表示进程内存大小的最好方式!
方案一:top指令
又是强大的top指令,可以查到每个进程占用的内存率,我们在man手册查看top指令,可以这样的描述:“ %MEM -- Memory Usage (RES):A task's currently used share of available physical memory.”,符合我们的需求。如果是一般情况下,推荐使用top指令获取进程相关数据,但用于性能监控程序,top指令还是太耗时了。
方案二:procrank指令
procrank是一个统计内存使用的神器,可以获取上述VSS,PSS,PSS和USS的详细参数,是一个分析内存使用非常有用的工具。然而计算出如此详细的参数自然需要较长的时间,因此在这里并不是最优解。
PID Vss Rss Pss Uss Swap PSwap USwap ZSwap cmdline
1894 8866976K 262320K 119897K 109052K 3336K 58K 0K 11K com.launcher
3479 7979016K 251528K 118276K 109140K 65728K 7437K 0K 1474K com.guide
945 8659104K 278584K 117064K 101728K 41800K 6061K 12K 1201K system_server
2482 8185712K 255492K 107944K 93516K 3536K 62K 0K 12K com.speech
2387 5820232K 159392K 64362K 60268K 4416K 79K 0K 15K com.sitting
...
方案三:dumpsys meminfo
dumpsys meminfo可以按PSS降序排序获取当前系统所有进程的内存占用情况,也包含进程名和对应pid,很适合需求,然而dumpsys meminfo耗时达到秒级,还是太耗时了。目前为止的三种方案耗时都是秒级,而且都是通过shell指令获取,其数据量较大,分析时较为麻烦。
Applications Memory Usage (in Kilobytes):
Uptime: 8646482 Realtime: 8646482
Total PSS by process:
263,269K: com.guide (pid 3479)
193,979K: system (pid 945)
166,109K: com.launcher (pid 1894 / activities)
145,046K: com.speech (pid 2482)
105,911K: surfaceflinger (pid 489)
92,802K: com.settings (pid 2349 / activities)
...
方案四:ps指令
ps指令相信很多人对它又熟悉又陌生,我们经常用ps查看进程信息,不出所料ps也可以查看进程占用内存的情况。在man手册查询ps指令,有如下描述:
“%MEM :ratio of the process's resident set size to the physical memory on the machine, expressed as a percentage.”。
也就是通过计算得到每个进程的RSS对应的百分比,我们再根据%MEM指标对所有进程进行降序排序,就可以获得内存使用率TOP5进程,指令如下:
ps -eo CMD,%MEM --sort=-%MEM
打印内容如下:
CMD %MEM
com.speech 4.6
com.launcher 4.2
system_server 3.8
main 2.7
...
通过测试,执行一遍ps指令耗时是毫秒级别,远优于上述方案1-3,同时对数据的解析也相对简单,虽然不是以USS排序,综合考虑推荐使用ps指令来获取内存使用率TOP5进程。
结论
| 内存使用率TOP5进程获取的方法 | 效率 | 实现难度 | 推荐 |
|---|---|---|---|
| top指令 | 低 | 中 | |
| procrank指令 | 低 | 中 | |
| dumpsys meminfo | 低 | 中 | |
| ps指令取 | 高 | 易 | ✔ |
5、网速获取
Android 在应用层提供了流量统计的工具类android.net.TrafficStats,这个统计包括字节数的发送和接收,网络包的发送和接等接口,为了获得接收和发送的字节量可以直接调用以下接口:
- getTotalRxBytes():获取总的接收字节数
- getTotalTxBytes():获取总的发送字节数 通过算计较短的时间间隔内接收/发送的字节数增量,也就可以获取到接收/发送的速度啦。方法比较通用,在此提供一个demo供参考。
val rcvTrafficPre = TrafficStats.getTotalRxBytes()
val trsTrafficPre = TrafficStats.getTotalTxBytes()
Timer().schedule(object : TimerTask() {
@RequiresApi(api = Build.VERSION_CODES.N)
override fun run() {
val rcvTrafficCur = TrafficStats.getTotalRxBytes()
val trsTrafficCur = TrafficStats.getTotalTxBytes()
val trafficSpeedUp = trsTrafficCur - trsTrafficPre
val trafficSpeedDown = rcvTrafficCur - rcvTrafficPre
val msg = mHandler.obtainMessage()
msg.what = HANDLER_MSG_WRITE_NET_SPEED
trafficSpeedStr = "$trafficSpeedUp,$trafficSpeedDown"
msg.obj = trafficSpeedStr
mHandler.sendMessage(msg)
}
}, 1000)
6、磁盘读写速度获取
我们都知道,目前市面上的安卓设备,长期所使用后系统流畅度会越来越卡顿,这是因为长期使用会产生的磁盘碎片,影响了磁盘的读写性能。因此监控一台设备的磁盘读写速度是有所必要的。
关于磁盘读写速度,目前通过读取/proc/vmstat文件,获取到以下两个指标:
- pgpgin:从启动到现在,从硬盘读取到物理内存的内存页数
- pgpgout:从启动到现在,从物理内存写入硬盘的内存页数 计算方法: 获取10秒内pgpgin的增量,把pgpgin的增量再除以10得到每秒的平均增量,即为平均每秒把数据从硬盘读到物理内存的数据量。pgpgout同理,可以得出平均每秒把数据从物理内存写到硬盘的数据量。 PS:执行getconf PAGESIZE 结果为4096, 即一页=4096字节=4KB. 以下提供获取pgpgin和pgpgout的demo:
val diskInfoResult: ShellCommandUtils.CommandResult =
ShellCommandUtils.executeCommand("cat /proc/vmstat |grep pgpg")
if (diskInfoResult.result == 0) {
val line: String = diskInfoResult.successMsg
val value = line.split("\n")
val pgpgin = value[0].split(" ")
val pgpgout = value[1].split(" ")
Log.d(TAG, "pgpgin = $pgpgin")
Log.d(TAG, "pgpgout = $pgpgout")
return DiskStatInfo(pgpgin = pgpgin[1].toLong(), pgpgout = pgpgout[1].toLong())
}
7、总结与展望
本文对CPU使用率、CPU使用率TOP5的进程、内存、内存占用TOP5进程、网络速度、磁盘速度这6个常见的系统性能指标的获取方式进行分析和优化,最终开发出一套Android系统通用性能监控工具。可灵活配置各项指标的获取和上报时间间隔,将收集到的数据保存在本地,等达到设定的上报时间将所有数据压缩后上报到后台。最终通过这些性能监控数据,找到自身性能短板,针对性的优化用户体验。
为更加全面地监控Android系统性能,下一步可考虑增加以下其他Android系统性能监控指标:
- CPU和内存的交互速度
- 系统温度
- 耗电量
- fps(用户可见的每秒显示帧数)
- 开机速度
- GPU负载
- 磁盘存储情况