python实时获取Android FPS

1:如何计算FPS信息?

标准的FPS 是 1s 60 帧,也就是16.67ms一帧渲染完成,用户不会觉得卡顿。但是大部分应用是不需要1s内绘制60帧,我们需要根据丢帧率去计算fps,如果1s内应用只需要绘制30帧,但每帧的时间是不超过16.67ms,那么它的fps也是60。
当一帧渲染的时间大于16.67ms,按照垂直同步机制,该帧就已经渲染超时,那么,如果它正好是16.67的整数倍,比如66.68,则它花费了4个垂直同步脉冲,减去本身需要一个,则超时3个;如果它不是16.67的整数倍,比如67,那么它花费的垂直同步脉冲应向上取整,即5个,减去本身需要一个,即超时4个,可直接算向下取整

最后的计算方法思路:
执行一次命令,总共收集到了m帧(理想情况下m=128),但是这m帧里面有些帧渲染超过了16.67毫秒,算一次jank,一旦jank,
需要用掉额外的垂直同步脉冲。其他的就算没有超过16.67,也按一个脉冲时间来算(理想情况下,一个脉冲就可以渲染完一帧)

所以FPS的算法可以变为:
m / (m + 额外的垂直同步脉冲) * 60

2:如何获取FPS信息?

Android性能测试之fps获取

安卓8.0之前使用dumpsys SurfaceFlinger 获取FPS信息详解(必看)
adb shell dumpsys SurfaceFlinger --latency <window_activity>

安卓9.0之后使用dumpsys gfxinfo framestats 获取FPS信息详解(必看)
adb shell dumpsys gfxinfo < PACKAGE_NAME > framestats
详细信息说明看上面链接
在这里插入图片描述

在这里插入图片描述

两种都是获取指定窗口的详细显示信息,需要了解每一列参数的详细信息

以9.0之后使用dumpsys gfxinfo 获取信息为例:
每一帧的时间就是 FRAME_COMPLETED - INTENDED_VSYNC ,时间是以ns为单位

3:python实现 fps实时获取和计算

使用adb shell dumpsys gfxinfo 获取fps信息

由于是根据包名获取每一帧的详细信息,且最多只能获取128帧,所以最理想的状态就是2s内手机绘制了120帧,但大部分应用2s内是不会绘制120帧的,所以根据实际项目需要,我们需要设置不同的时间间隔去获取fps,此处我们使用2s间隔获取一次fps数据,相同时间内,绘制的帧越多,则fps越准确。
self.frequency = 2 取样频率 就是每间隔2秒获取一次fps信息
sn是设备id
_collector_thread 是用来获取fps信息的线程
_calculator_thread 是用来计算fps的线程
定义大于16.67ms为丢帧 jank
定义大于166.7ms为卡顿 caton

1:在开始获取fps信息之前,我们先进行获取当前活动窗口,get_focus_window

adb shell dumpsys activity | findstr mResume(适用于安卓9.0之后的,安卓8.0之前可使用adb shell dumpsys activity | findstr “mFocusedActivity”)

C:\Users\ysfa>adb shell dumpsys activity | findstr mResume
    mResumedActivity: ActivityRecord{
    
    db71ee8 u0 com.android.settings/.SubSettings t1572}

com.android.settings/.SubSettings 这个则是当前手机上活动的窗口名(包名省略为.)
注意! 部分三方应用的窗口会把包名全部显示,例如com.eg.android.AlipayGphone/com.alipay.mobile.security.login.ui.RecommandAlipayUserLoginActivity


C:\Users\ysfa>adb shell dumpsys activity | findstr mResume
    mResumedActivity: ActivityRecord{
    
    e84c958 u0 com.eg.android.AlipayGphone/com.alipay.mobile.security.login.ui.RecommandAlipayUserLoginActivity t1576}

拿到窗口名,我们可以提取出包名+窗口名的信息

因为使用shell dumpsys gfxinfo +包名+framestats 获取的这个进程的最近128帧的的详细信息,可能包含多个窗口(也可能包含重复帧),而信息中的窗口名是全名,没有.省略包名,所以我们需要自己拼出完整的窗口名
如将com.android.settings/.SubSettings 改成com.android.settings/com.android.settings.SubSettings

**

2:在开始获取fps信息之前,我们先进行清除当前窗口fps数据

"adb shell dumpsys gfxinfo " + package_name + " reset"

3:使用shell dumpsys gfxinfo +包名 + framestats 获取的这个进程的最近128帧的的详细信息,可能包含多个窗口(也可能包含重复帧,也可能没有128帧)

例如下面则是有两个窗口

C:\Users\ysfa>adb shell dumpsys gfxinfo com.android.settings framestats
Applications Graphics Acceleration Info:
Uptime: 56776419 Realtime: 113853289

** Graphics info for pid 15490 [com.android.settings] **

Stats since: 43790991914179ns
Total frames rendered: 1021
Janky frames: 52 (5.09%)
50th percentile: 6ms
90th percentile: 11ms
95th percentile: 16ms
99th percentile: 77ms
Number Missed Vsync: 11
Number High input latency: 182
Number Slow UI thread: 30
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 1
Number Frame deadline missed: 31
HISTOGRAM: 5ms=486 6ms=157 7ms=112 8ms=65 9ms=49 10ms=27 11ms=33 12ms=18 13ms=13 14ms=2 15ms=6 16ms=2 17ms=2 18ms=0 19ms=4 20ms=1 21ms=1 22ms=0 23ms=4 24ms=1 25ms=1 26ms=2 27ms=1 28ms=3 29ms=0 30ms=0 31ms=0 32ms=3 34ms=0 36ms=1 38ms=1 40ms=4 42ms=2 44ms=3 46ms=0 48ms=0 53ms=2 57ms=2 61ms=0 65ms=0 69ms=1 73ms=1 77ms=3 81ms=2 85ms=0 89ms=1 93ms=0 97ms=0 101ms=0 105ms=1 109ms=1 113ms=0 117ms=0 121ms=0 125ms=1 129ms=0 133ms=0 150ms=2 200ms=0 250ms=0 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0
Font Cache (CPU):
  Size: 623.27 kB
  Glyph Count: 41
CPU Caches:
GPU Caches:
  Other:
    Other: 0.00 bytes (1 entry)
    Buffer Object: 2.05 KB (2 entries)
  Image:
    Texture: 151.12 KB (13 entries)
  Scratch:
    RenderTarget: 9.00 KB (1 entry)
    Buffer Object: 48.00 KB (1 entry)
    Texture: 4.00 MB (1 entry)
Other Caches:
                         Current / Maximum
  VectorDrawableAtlas    0.00 kB /   0.00 KB (entries = 0)
  Layers Total           0.00 KB (numLayers = 0)
Total GPU memory usage:
  4409524 bytes, 4.21 MB (153.18 KB is purgeable)


Pipeline=Skia (OpenGL)

Layout Cache Info:
  Usage: 513/5000 entries
  Hit ratio: 15858/16371 (0.968664)
Profile data in ms:

        com.android.settings/com.android.settings.Settings/android.view.ViewRootImpl@f131882 (visibility=8)
Window: com.android.settings/com.android.settings.Settings
Stats since: 52644835521687ns
Total frames rendered: 5
Janky frames: 2 (40.00%)
50th percentile: 5ms
90th percentile: 53ms
95th percentile: 53ms
99th percentile: 53ms
Number Missed Vsync: 0
Number High input latency: 2
Number Slow UI thread: 1
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 0
Number Frame deadline missed: 1
HISTOGRAM: 5ms=3 6ms=0 7ms=0 8ms=0 9ms=0 10ms=0 11ms=0 12ms=0 13ms=0 14ms=0 15ms=0 16ms=0 17ms=0 18ms=0 19ms=0 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=0 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 42ms=1 44ms=0 46ms=0 48ms=0 53ms=1 57ms=0 61ms=0 65ms=0 69ms=0 73ms=0 77ms=0 81ms=0 85ms=0 89ms=0 93ms=0 97ms=0 101ms=0 105ms=0 109ms=0 113ms=0 117ms=0 121ms=0 125ms=0 129ms=0 133ms=0 150ms=0 200ms=0 250ms=0 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0


---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
1,52644835725530,52644835725530,9223372036854775807,0,52644839507156,52644839521322,52644839522208,52644882804499,52644884679760,52644884854083,52644884964447,52644890317208,52644890604395,398000,115000,
0,52644852435274,52644885768606,9223372036854775807,0,52644891648406,52644891663354,52644892190333,52644892544864,52644894457364,52644894569864,52644894716947,52644896089499,52644896420489,323000,130000,
0,52644902567663,52644902567663,9223372036854775807,0,52644903024135,52644903036687,52644903037624,52644903244812,52644903515385,52644903659551,52644903796322,52644905614239,52644906009603,485000,155000,
0,52645654850958,52645654850958,9223372036854775807,0,52645655074707,52645655090697,52645655092832,52645655544968,52645655855541,52645656225228,52645656841530,52645659397416,52645659824343,353000,136000,
0,52645688284367,52645688284367,9223372036854775807,0,52645688486895,52645688497624,52645688498874,52645688666322,52645688838822,52645688971218,52645689081166,52645689787416,52645690071426,71000,102000,
---PROFILEDATA---


        com.android.settings/com.android.settings.SubSettings/android.view.ViewRootImpl@3253b93 (visibility=8)
Window: com.android.settings/com.android.settings.SubSettings
Stats since: 52648079271686ns
Total frames rendered: 135
Janky frames: 9 (6.67%)
50th percentile: 7ms
90th percentile: 12ms
95th percentile: 23ms
99th percentile: 44ms
Number Missed Vsync: 1
Number High input latency: 39
Number Slow UI thread: 5
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 0
Number Frame deadline missed: 5
HISTOGRAM: 5ms=39 6ms=15 7ms=27 8ms=13 9ms=9 10ms=3 11ms=10 12ms=7 13ms=2 14ms=0 15ms=1 16ms=0 17ms=0 18ms=0 19ms=0 20ms=1 21ms=0 22ms=0 23ms=2 24ms=0 25ms=0 26ms=1 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=2 34ms=0 36ms=0 38ms=0 40ms=1 42ms=0 44ms=1 46ms=0 48ms=0 53ms=0 57ms=1 61ms=0 65ms=0 69ms=0 73ms=0 77ms=0 81ms=0 85ms=0 89ms=0 93ms=0 97ms=0 101ms=0 105ms=0 109ms=0 113ms=0 117ms=0 121ms=0 125ms=0 129ms=0 133ms=0 150ms=0 200ms=0 250ms=0 300ms=0 350ms=0 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=0 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0


---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,52649246791970,52649246791970,9223372036854775807,0,52649247008977,52649247016633,52649247083560,52649247252883,52649247318508,52649247419133,52649247507258,52649247726894,52649247943248,50000,68000,
0,52649263499755,52649263499755,9223372036854775807,0,52649263745904,52649263753456,52649263821425,52649263966112,52649264042935,52649264141008,52649264225852,52649264446789,52649264594394,52000,75000,
0,52649280172678,52649280172678,9223372036854775807,0,52649280390643,52649280398821,52649280492779,52649280826789,52649280901841,52649280991789,52649281092258,52649282058143,52649282244029,719000,84000,
0,52649296908974,52649296908974,9223372036854775807,0,52649297192571,52649297200956,52649297272987,52649297500748,52649297570018,52649297660071,52649297755123,52649298330800,52649298491789,388000,79000,
0,52649314226393,52649314226393,9223372036854775807,0,52649314576321,52649314590643,52649314682414,52649314878300,52649314976946,52649315080487,52649315199237,52649316256841,52649316463873,718000,87000,
0,52649330956704,52649330956704,9223372036854775807,0,52649331218456,52649331229706,52649331328925,52649331494654,52649331568821,52649331661529,52649331772883,52649332717258,52649332905748,693000,84000,
0,52649347049758,52649347049758,9223372036854775807,0,52649347281893,52649347293143,52649347379706,52649347608196,52649347693039,52649347778196,52649347890591,52649348834498,52649349042987,683000,102000,
0,52649697887620,52649697887620,9223372036854775807,0,52649702086373,52649702120643,52649702124862,52649703167310,52649708892727,52649709473560,52649710831060,52649717494654,52649718642466,499000,464000,
0,52649714600252,52649714600252,9223372036854775807,0,52649715011216,52649715042466,52649715046477,52649715644810,52649719492414,52649719910852,52649720709706,52649722900956,52649723371060,160000,184000,
0,52649731310975,52649731310975,9223372036854775807,0,52649731588612,52649731612362,52649731614758,52649732029341,52649734277570,52649734497466,52649735169029,52649737701372,52649738107883,160000,161000,
0,52649748021669,52649748021669,9223372036854775807,0,52649748340227,52649748360800,52649748363664,52649748845122,52649751609810,52649751923977,52649752743716,52649755433664,52649755953768,149000,243000,
0,52649764732139,52649764732139,9223372036854775807,0,52649765139289,52649765163195,52649765167258,52649765903091,52649769690539,52649770072206,52649770664966,52649774161997,52649774849550,193000,338000,
0,52649781442682,52649781442682,9223372036854775807,0,52649781792310,52649781814237,52649781818195,52649782385487,52649785698872,52649786096425,52649786659341,52649790374706,52649791046529,224000,310000,
0,52649798153253,52649798153253,9223372036854775807,0,52649798517831,52649798542050,52649798546060,52649799112883,52649802161268,52649802525435,52649803129602,52649806656581,52649807274602,240000,271000,
0,52649814863694,52649814863694,9223372036854775807,0,52649815799706,52649815828508,52649815832570,52649817335487,52649820929497,52649821328195,52649822128768,52649825968872,52649826741268,241000,395000,
0,52649831574226,52649831574226,9223372036854775807,0,52649831939654,52649831963300,52649831967414,52649832604602,52649835772466,52649836293404,52649836986112,52649840806581,52649841405018,223000,265000,
0,52649848284615,52649848284615,9223372036854775807,0,52649849011372,52649849041425,52649849045018,52649851157727,52649854439497,52649854777466,52649855739810,52649859079862,52649859603872,357000,228000,
0,52649864995380,52649864995380,9223372036854775807,0,52649865322362,52649865343664,52649865346425,52649865810487,52649868761529,52649869067883,52649869989289,52649873253768,52649873828560,151000,228000,
0,52649881706029,52649881706029,9223372036854775807,0,52649882549810,52649882577779,52649882580383,52649883775695,52649886574081,52649886904029,52649887672102,52649890712675,52649891259497,465000,218000,
0,52649898416683,52649898416683,9223372036854775807,0,52649898804237,52649898834810,52649898837675,52649899376841,52649902517570,52649902790695,52649903661320,52649906252206,52649906706841,149000,180000,
0,52649915127446,52649915127446,9223372036854775807,0,52649915427518,52649915452102,52649915454185,52649916730487,52649918999810,52649919207883,52649919950227,52649922103925,52649922502102,232000,163000,
0,52649931838312,52649931838312,9223372036854775807,0,52649932090852,52649932108508,52649932110279,52649932414654,52649934322987,52649934504445,52649935012831,52649936734237,52649937059185,111000,138000,
0,52649948548982,52649948548982,9223372036854775807,0,52649949961789,52649949987206,52649949990174,52649951342518,52649954262622,52649954549081,52649955380070,52649961340904,52649961895487,171000,228000,
0,52649965257509,52649965257509,9223372036854775807,0,52649966073612,52649966105956,52649966108820,52649966579862,52649968551477,52649968823560,52649969662414,52649973552102,52649974074654,958000,200000,
0,52649998612728,52649998612728,9223372036854775807,0,52649998977622,52649999557206,52649999559549,52650017059133,52650020417883,52650020646320,52650021568768,52650025146633,52650025758195,481000,200000,
0,52650015326027,52650015326027,9223372036854775807,0,52650021651945,52650021653299,52650022256320,52650022672935,52650023145487,52650025904237,52650026439497,52650028692154,52650029208664,104000,212000,
0,52650032044406,52650032044406,9223372036854775807,0,52650032325539,52650032351633,52650032628664,52650032995174,52650033231164,52650033404549,52650033809602,52650036246529,52650036571424,549000,144000,
0,52650048712678,52650048712678,9223372036854775807,0,52650049223247,52650049248352,52650049509706,52650050059966,52650050409289,52650050707310,52650051197727,52650052948872,52650053928299,303000,628000,
0,52650065451017,52650065451017,9223372036854775807,0,52650065783091,52650065811372,52650065992570,52650067972883,52650068491477,52650068892466,52650069592570,52650070378560,52650072193039,135000,1567000,
0,52650082148943,52650082148943,9223372036854775807,0,52650082448872,52650082474810,52650082711737,52650084045956,52650084379341,52650084573508,52650085195070,52650085874081,52650086263404,119000,172000,
0,52650098856086,52650098856086,9223372036854775807,0,52650099223768,52650099243977,52650099439602,52650099924549,52650101026008,52650101260956,52650101923872,52650102694966,52650103103768,157000,166000,
0,52650115562739,52650115562739,9223372036854775807,0,52650115944706,52650115969341,52650116332310,52650116949081,52650117979133,52650118371424,52650118993977,52650120497466,52650121246477,340000,302000,
0,52650132269055,52650132269055,9223372036854775807,0,52650132743247,52650132778091,52650133080070,52650133797727,52650135069497,52650135579133,52650136537727,52650138084862,52650138892154,492000,336000,
0,52650148975500,52650148975500,9223372036854775807,0,52650149621216,52650149650226,52650149885174,52650150524654,52650151176372,52650151718091,52650152564914,52650153554601,52650154188664,193000,308000,
0,52650165681977,52650165681977,9223372036854775807,0,52650166140122,52650166172154,52650166478143,52650167273612,52650168604758,52650169180226,52650169986893,52650171338299,52650172238247,370000,447000,
0,52650182388296,52650182388296,9223372036854775807,0,52650182942779,52650182972362,52650183367101,52650183978456,52650185713039,52650186415591,52650187213195,52650189186529,52650189928716,924000,340000,
0,52650199094781,52650199094781,9223372036854775807,0,52650199689549,52650199830956,52650200480226,52650200939445,52650202084914,52650202350226,52650203078404,52650203995383,52650204613351,215000,218000,
0,52650215800540,52650215800540,9223372036854775807,0,52650216403976,52650216429393,52650217027206,52650217563716,52650218779810,52650219244497,52650219859706,52650221868820,52650223560747,676000,1300000,
0,52650232507063,52650232507063,9223372036854775807,0,52650232925226,52650232957935,52650233205226,52650233922570,52650234452726,52650234798508,52650235375174,52650236484289,52650237067518,223000,261000,
0,52650249213762,52650249213762,9223372036854775807,0,52650249567049,52650249631841,52650249828247,52650250196841,52650250466216,52650252292518,52650253297466,52650254284654,52650254833195,208000,256000,
---PROFILEDATA---


View hierarchy:

  com.android.settings/com.android.settings.Settings/android.view.ViewRootImpl@f131882
  255 views, 270.94 kB of display lists

  com.android.settings/com.android.settings.SubSettings/android.view.ViewRootImpl@3253b93
  274 views, 291.13 kB of display lists


Total ViewRootImpl: 2
Total Views:        529
Total DisplayList:  562.06 kB

例如下面则是有两个窗口

Window: com.android.settings/com.android.settings.SubSettings(窗口一)
Window: com.android.settings/com.android.settings.Settings(窗口二)

我们需要根据上面获取的当前活动窗口(包名不能省略,如果包名省略了,需要转换为全名),去筛选出当前正在活动窗口的fps信息,也就是PROFILEDATA的中间数据
原理:根据返回的信息中含有window去定位到当前窗口,第一个—PROFILEDATA— 中的数据就是当前窗口的详细信息

详细方法看_get_fps_data

---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
1,52644835725530,52644835725530,9223372036854775807,0,52644839507156,52644839521322,52644839522208,52644882804499,52644884679760,52644884854083,52644884964447,52644890317208,52644890604395,398000,115000,
0,52644852435274,52644885768606,9223372036854775807,0,52644891648406,52644891663354,52644892190333,52644892544864,52644894457364,52644894569864,52644894716947,52644896089499,52644896420489,323000,130000,
0,52644902567663,52644902567663,9223372036854775807,0,52644903024135,52644903036687,52644903037624,52644903244812,52644903515385,52644903659551,52644903796322,52644905614239,52644906009603,485000,155000,
0,52645654850958,52645654850958,9223372036854775807,0,52645655074707,52645655090697,52645655092832,52645655544968,52645655855541,52645656225228,52645656841530,52645659397416,52645659824343,353000,136000,
0,52645688284367,52645688284367,9223372036854775807,0,52645688486895,52645688497624,52645688498874,52645688666322,52645688838822,52645688971218,52645689081166,52645689787416,52645690071426,71000,102000,
---PROFILEDATA---

4:去除重复帧

记录当前窗口的最后一次时间(第一次取0,可能会存在一些误差,所以测试之前我们需要清除数据),遍历PROFILEDATA中IntendedVsync(每帧的起始时间)的时间大于最后一次记录的时间,这时候就不是重复帧,才进行采集数据

  # 我们需要在次数去除重复帧,通过每帧的起始时间去判断是否是重复的
        for timestamp in each_frame_timestamps:
            if timestamp[0] > self.last_timestamp:
                timestamps.append((timestamp[1] - timestamp[0]) / 1000000)
                self.last_timestamp = timestamp[0]
        return timestamps, int(phone_time)

5:计算FPS,jank,卡顿

每帧耗时时间 time = (FRAME_COMPLETED - INTENDED_VSYNC)/1000000 (单位ms)
最终去除重复帧后,集合中有多少行就是有多少帧绘制,大于16.67ms的则是jank ,大于166.7ms则是卡顿

总共收集到了m帧(理想情况下m=128),但是这m帧里面有些帧渲染超过了16.67毫秒,算一次jank,一旦jank,
需要用掉额外的垂直同步脉冲。其他的就算没有超过16.67,也按一个脉冲时间来算(理想情况下,一个脉冲就可以渲染完一帧)

所以FPS的算法可以变为:
m / (m + 额外的垂直同步脉冲) * 60

6:python脚本实现

FpsInfo 是结构体
FpsListenserImpl 是定义的接口实现,用来调用显示和打印的,调试时可删除
功能就是每隔2s获取当前活动窗口的fps,并统计丢帧数和丢帧的耗时时间

执行结果如下

C:\Users\ysfa\AppData\Local\Programs\Python\Python37\python.exe D:/PycharmProjects/untitled/fps.py
FPS monitor has start!
已经清除FPS数据,请3秒后开始滑动界面


当前进程是:com.android.settings
当前窗口是:com.android.settings/com.android.settings.SubSettings
当前手机窗口刷新时间:2020-07-30 19:59:57
当前窗口fps是:59.0625
当前2s获取总帧数:63
当前窗口丢帧数>16.67ms)是:1
[24.982251]
当前窗口卡顿数(>166.7ms)是:0




当前进程是:com.android.settings
当前窗口是:com.android.settings/com.android.settings.SubSettings
当前手机窗口刷新时间:2020-07-30 19:59:59
当前窗口fps是:60.0
当前2s获取总帧数:41
当前窗口丢帧数>16.67ms)是:0
[]
当前窗口卡顿数(>166.7ms)是:0
import os
import queue
import threading
import time
from math import floor

from FpsInfo import FpsInfo
from FpsListenserImpl import FpsListenserImpl


class FPSMonitor(object):
    def __init__(self, sn):
        self.frequency = 2  # 取样频率
        self.device = sn
        self.fpscollector = FpsCollector(self.device, self.frequency)

    def set_listener(self, listener):
        self.fpscollector.set_listener(listener)

    def start(self, start_time):
        self.start_time = start_time

        if self.fpscollector.package_name is None:
            print("手机没亮屏,或者usb未连接")
            return
        print('FPS monitor has start!')
        self.fpscollector.start(start_time)

    def stop(self):
        '''结束FPSMonitor日志监控器

        '''
        if self.fpscollector.package_name is None:
            print("手机没亮屏,或者usb未连接")
            return
        self.fpscollector.stop()
        print('FPS monitor has stop!')

    def save(self):
        pass

    def parse(self, file_path):
        '''解析

        :param str file_path: 要解析数据文件的路径

        '''

        pass

    def get_fps_collector(self):
        '''获得fps收集器,收集器里保存着time fps jank的列表



        :return: fps收集器

        :rtype: SurfaceStatsCollector

        '''

        return self.fpscollector


class FpsCollector(object):
    '''Collects surface stats for a SurfaceView from the output of SurfaceFlinger
    '''

    def __init__(self, device, frequency):
        self.device = device
        self.frequency = frequency
        self.jank_threshold = 16.7  # 内部的时间戳是毫秒秒为单位
        self.last_timestamp = 0  # 上次最后最早一帧的时间
        self.data_queue = queue.Queue()
        self.stop_event = threading.Event()
        self.get_focus_window()
        self.listener = None
        #       queue 上报线程用

    def start(self, start_time):

        '''打开SurfaceStatsCollector

        '''
        self._clear_fps_data()
        self.collector_thread = threading.Thread(target=self._collector_thread)

        self.collector_thread.start()

        self.calculator_thread = threading.Thread(target=self._calculator_thread, args=(start_time,))

        self.calculator_thread.start()

    def stop(self):

        '''结束SurfaceStatsCollector

        '''

        if self.collector_thread:
            self.stop_event.set()

            self.collector_thread.join()

            self.collector_thread = None

    def set_listener(self, listener):
        self.listener = listener

    def get_focus_window(self):

        '''通过adb shell dumpsys activity | findstr "mResume"

        '''
        cmd = "adb -s " + self.device + " shell dumpsys activity | findstr mResume"
        # print(cmd)
        windowInfo = os.popen(cmd)
        windowInfo = str(windowInfo.readline())
        # print(windowInfo)
        if windowInfo is "":
            self.package_name = None
            self.focus_window = None
            return
        packageNameinfo = windowInfo.split(" ")[7]
        packageName = packageNameinfo.split("/")[0]
        if "/." in packageNameinfo:
            windowName = packageName + "/" + packageName + "." + packageNameinfo.split("/.")[1]
        else:
            windowName = packageNameinfo
        self.package_name = packageName
        self.focus_window = windowName

    def _calculate_results(self, timestamps):

        """Returns a list of SurfaceStatsCollector.Result.
        FPS  丢帧率  卡顿次数  总帧数

        """
        frame_count = len(timestamps)
        jank_list, caton, vsyncOverTimes = self._calculate_janky(timestamps)
        fps = frame_count / (frame_count + vsyncOverTimes) * 60
        return fps, jank_list, caton

    def _calculate_janky(self, timestamps):
        # 统计丢帧卡顿 ,和需要垂直同步次数
        jank_list = []
        caton = 0
        vsyncOverTimes = 0
        for timestamp in timestamps:
            if timestamp > self.jank_threshold:
                # 超过16.67ms
                jank_list.append(timestamp)
                if timestamp % self.jank_threshold == 0:
                    vsyncOverTimes += ((timestamp / self.jank_threshold) - 1)
                else:
                    vsyncOverTimes += floor(timestamp / self.jank_threshold)
            if timestamp > self.jank_threshold * 10:
                # 超过166.7ms 明显卡的帧,用户会觉得卡顿
                caton += 1
        return jank_list, caton, vsyncOverTimes

    def _calculator_thread(self, start_time):
        '''处理surfaceflinger数据
        '''
        while True:
            try:
                data = self.data_queue.get()
                # print(data)
                if isinstance(data, str) and data == 'Stop':
                    break
                # before = time.time()
                refresh_time = int(data[0])
                # print(refresh_time)
                timestamps = data[1]
                fps, jank_list, caton = self._calculate_results(timestamps)
                fps_info = FpsInfo(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(refresh_time)), len(timestamps),
                                   fps,
                                   self.package_name, self.focus_window, jank_list, len(jank_list), caton)
                self.listener.report_fps_info(fps_info)
                # print('\n')
                # print("当前设备是:" + self.device)
                # print("当前进程是:" + self.package_name)
                # print("当前窗口是:" + self.focus_window)
                # print("当前手机窗口刷新时间:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(refresh_time)))
                # print("当前窗口fps是:" + str(fps))
                # print("当前2s获取总帧数:" + str(len(timestamps)))
                # print("当前窗口丢帧数>16.67ms)是:" + str(jank_list))
                # print("当前窗口卡顿数(>166.7ms)是:" + str(caton))
                # print('\n')
            except Exception as  e:
                print(e)
                print("计算fps数据异常")
                self.data_queue.put('Stop')
                if self.calculator_thread:
                    self.stop_event.set()
                    self.calculator_thread = None
                return

    def _collector_thread(self):
        '''收集FPS数据
        shell dumpsys gfxinfo 《 window》 framestats
        3
        '''
        last_refresh_time = 0
        while not self.stop_event.is_set():
            # 此处进行获取,并将数据存放在data_quue里
            try:
                before = time.time()
                # print("开始获取fps信息:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(before)))
                self.get_focus_window()
                new_timestamps, now_refresh_time = self._get_fps_data()
                # 此处需要检查是否获取数据成功
                if now_refresh_time is None or new_timestamps is None:
                    # 这里可能就是清楚数据后,没有做界面操作,所以会拿不到数据,跳过,我们获取下一次的
                    continue
                # print(new_timestamps)
                # 大于则证明有帧信息刷新,无则不需要更新信息
                if self.last_timestamp > last_refresh_time:
                    last_refresh_time = self.last_timestamp
                    # print(last_refresh_time)
                    self.data_queue.put((now_refresh_time, new_timestamps))
                time_consume = time.time() - before
                delta_inter = self.frequency - time_consume
                if delta_inter > 0:
                    time.sleep(delta_inter)
                    # print('\n')
                    # print("结束获取fps信息:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
            except Exception as  e:
                print("获取fps数据异常")
                print(e)
                self.data_queue.put('Stop')
                if self.collector_thread:
                    self.stop_event.set()
                    self.collector_thread = None
                return
        if self.stop_event.is_set():
            self.data_queue.put('Stop')

    def _clear_fps_data(self):
        os.popen("adb -s " + self.device + " shell dumpsys gfxinfo " + self.package_name + " reset")
        # 清除数据后,直接获取fps会有异常,我们最好等待一段时间
        print("已经清除FPS数据,请3秒后开始滑动界面")
        time.sleep(5)

    def _get_fps_data(self):
        """
        isHaveFoundWindow  是否是当前活动窗口
        total_frames 总帧数
        timestamps  每帧耗时信息
        :rtype:
        :return:
        """
        results = os.popen("adb -s " + self.device + " shell dumpsys gfxinfo " + self.package_name + " framestats")
        phone_time = os.popen("adb -s " + self.device + " shell date +%s")
        phone_time = phone_time.readlines()[0]
        # print(phone_time)
        timestamps = []
        each_frame_timestamps = []
        isHaveFoundWindow = False
        PROFILEDATA_line = 0
        # 行数代表当前窗口总帧数,列数是每帧耗时详细信息
        # !!!注意一个进程可能存在多个窗口,所以我们只获取当前显示窗口的信息
        for line in results.readlines():
            # print("test" + line)
            if "Window" in line and self.focus_window in line:
                isHaveFoundWindow = True
                # print("focus Window is :" + line)
                continue
            if isHaveFoundWindow and "---PROFILEDATA---" in line:
                PROFILEDATA_line += 1
                # print(PROFILEDATA_line)
                continue
            if isHaveFoundWindow and "Flags,IntendedVsync," in line:
                continue
            if isHaveFoundWindow and PROFILEDATA_line is 1:
                # 此处代表的是当前活动窗口
                # 我们取PROFILEDATA中间的数据 最多128帧,还可能包含之前重复的帧,所以我们间隔1.5s就取一次数据
                fields = []
                fields = line.split(",")
                each_frame_timestamp = [float(fields[1]), float(fields[13])]
                each_frame_timestamps.append(each_frame_timestamp)
                continue
            if PROFILEDATA_line >= 2:
                break
        # 我们需要在次数去除重复帧,通过每帧的起始时间去判断是否是重复的
        for timestamp in each_frame_timestamps:
            if timestamp[0] > self.last_timestamp:
                timestamps.append((timestamp[1] - timestamp[0]) / 1000000)
                self.last_timestamp = timestamp[0]
        return timestamps, int(phone_time)

    def run_adb(cmd):
        return os.popen(cmd)


if __name__ == '__main__':
    sn = "9365fc0e"
    monitor = FPSMonitor(sn)
    lisenter = FpsListenserImpl()
    monitor.set_listener(lisenter)
    monitor.start(time.time())
    time.sleep(60)
    monitor.stop()

#!/usr/bin/env python
# coding:utf-8
"""
Name : FpsInfo.py
Author  :
Contect : 
Time    : 2020/7/21 14:26
Desc:
"""


class FpsInfo(object):
    def __init__(self, time, total_frames, fps, pkg_name, window_name, jankys_ary, jankys_more_than_16,
                 jankys_more_than_166):
        # 采样数据时的时间戳
        self.time = time
        # 2s内取到总帧数
        self.total_frames = total_frames
        # fps
        self.fps = fps
        # 测试应用包名
        self.pkg_name = pkg_name
        # 窗口名
        self.window_name = window_name
        # 掉帧具体时间集合
        self.jankys_ary = jankys_ary
        # 掉帧数目,大于16.67ms
        self.jankys_more_than_16 = jankys_more_than_16
        # 卡顿数目,大于166.7ms
        self.jankys_more_than_166 = jankys_more_than_166

#!/usr/bin/env python
# coding:utf-8
"""
Name : FpsListener.py
Author  : 
Contect : 
Time    : 2020/7/23 16:07
Desc:
"""
from abc import ABCMeta, abstractmethod

class IFpsListener(object):

    @abstractmethod
    def report_fps_info(self, fps_info):
        pass


from FpsListener import IFpsListener


class FpsListenserImpl(IFpsListener):
    def __init__(self):
        pass

    def report_fps_info(self, fps_info):
        print('\n')
        # print("当前设备是:" + fps_info.)
        print("当前进程是:" + str(fps_info.pkg_name))
        print("当前窗口是:" + str(fps_info.window_name))
        print("当前手机窗口刷新时间:" + str(fps_info.time))
        print("当前窗口fps是:" + str(fps_info.fps))
        print("当前2s获取总帧数:" + str(fps_info.total_frames))
        print("当前窗口丢帧数>16.67ms)是:" + str(fps_info.jankys_more_than_16))
        print(fps_info.jankys_ary)
        print("当前窗口卡顿数(>166.7ms)是:" + str(fps_info.jankys_more_than_166))
        print('\n')

猜你喜欢

转载自blog.csdn.net/weixin_42233460/article/details/107698909
FPS