有贊移動性能監控平台(一)


前言

隨着移動端業務復雜度的提升,開發同學在編寫業務的時候往往容易忽略性能問題,雖然有贊移動端自研了 APM ,但是 APM 采集的都是線上的數據,無法在 QA 與開發階段提前發現問題,為了保障軟件的穩定性,需要補齊線下監控能力,避免性能問題上線對商家經營過程造成影響。

一、架構設計

整體基於 APM 現有框架迭代線下監控能力,並在端上開發 AWACS 可視化工具,通過全局懸浮窗,並結合提醒能力(彈窗與 Toast 提示)實時通知測試人員進行問題查看,同時后台也會定時分析測試環境采集的性能數據,進行管理與分配。

QA 同學基於 Appium 補齊了 UI 主流程 case ,通過自動化測試最大程度確保每次應用發版的穩定。而設備自動化回歸流程可以與線下監控完美的結合起來,首先每個版本可以確保運行在同一款設備上,其次每個版本運行的主流程用例基本上保持一致,這樣就為應用“前后”版本性能數據分析對比提供了兩個參照條件。性能問題上報后,除了微信Robot通知相關干系人解決問題外,還基於移動端 mPaaS 搭建了問題管理與分配平台,便於跟進與追蹤。

二、監控指標分析

性能監控目前對階段、流量、頁面耗時、 ANR 、慢方法、 fps 等數據做了實時監控,本篇文章只會對階段、流量、頁面耗時進行歸納分析,后面“有贊移動性能監控平台系列文章“會對 ANR 、慢方法、 fps 等監控數據進行總結。

2.1 階段數據

移動端每個業務流程都可以統稱為“階段”,比如 App 啟動、商品加購、商品查詢等,業務方可以對自身需要關心的業務階段進行監控,結合“數據分析”與“告警能力”快速協助業務方排查問題。階段分析包括“方法耗時分析”與“網絡狀況分析”兩個部分,下面會具體介紹。

2.1.1 方法耗時分析

在 App 編譯期會對每個方法進行前后打點,確保運行過程中每個階段方法耗時都可以被自動統計出來,節省手動打點統計成本。

原理

開發 Gradle Plugin 插件,在 App 編譯 Transform 階段( .class 轉換為 .dex 過程),對字節碼進行操作,在方法的開始執行( methodEnter )與結束執行( methodExit )通過 ASM 工具分別插入 MethodBeat 的 i 與 o 方法,對每個方法進行首尾打點,運行時自動統計方法耗時。

分析詳情

應用所有版本產生的階段數據都會上傳到后端,后端會通過定式任務對階段數據進行分析,分析角度分為新增、新減、陡增、陡降4個維度,協助開發綜合對問題進行排查。啟動階段舉例:

2.1.2 網絡狀況分析

業務方可以定義是否需要監控階段的網絡狀況,比如啟動階段,除了要監控啟動方法耗時之外,網絡狀態也需要進行監控( App 啟動時,硬件負載比較高,過多的網絡 IO 請求會拖累啟動速度)。

原理

有贊零售 App 網絡通過 OkHttp 進行請求,通過自定義攔截器對網絡進行統一攔截,統計每個階段網絡鏈接數量與請求耗時, intercept 方法實現如下:

public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        String url = getRequestUrl(request.url());
        if (!TextUtils.isEmpty(url)) {
            AppSegmentCache.INSTANCE.setRequestStart(url);
            long startNs = System.nanoTime();
            try {
                response = chain.proceed(request);
            } catch (Exception e) {
                throw e;
            }
            long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            AppSegmentCache.INSTANCE.setRequestEnd(url, tookMs);
        }
        return chain.proceed(request);
    }


分析詳情

分為“全部調用”與"重復調用"兩個統計維度,“全部調用”會統計當前版本該階段網絡請求總數,且會與上個版本請求總數進行比較,計算出升降趨勢(提升/下降了 x%),且列出“新增”與“新減”兩個數據維度,協助對網絡狀況進行分析。“重復調用”會統計所有重復調用的接口狀況,包括網絡重復請求的總數,還會全部列出重復調用鏈接內容,方便業務方進行排查優化。

2.2 流量

App 運行過程中主要涉及到接口、文本、視頻、圖片等各種流量請求,往往在開發過程中不太會注意流量消耗這個指標,最近也經常有商家反饋 App 流量消耗比較大,但目前並不能准確的定位流量消耗主因。

2.2.1 原理

分別對 HttpUrlConnection 和 OkHttp 做 Hook ( .class -> .dex transform 流程插樁),所有的請求都得經過 Hook 層,這樣就能統計每個請求的流量大小與 response 內容,便於業務方進行分析。

OkHttpHook

App 編譯期往 OkHttp 中配置 GlobalNetworkInterceptor 攔截器,統計 response length(流量大小)、response content(請求返回內容,如果是 gzip 需要解壓),然后將流量內容寫入文件中(非線上包才會采集),便於分析。

internal object OkHttpHook {
    @JvmField
    public val globalNetworkInterceptor = Interceptor { chain ->
    ... ...
    // 計算repsonse length(流量大小)
    // 讀取response content(如果是gzip需要解壓)
    // 流量內容寫入文件中
    val fileUrl = File(file, URLEncoder.encode(SimpleDateFormat("yyyy-MM-dd-HH:mm:ss-SSS").format(Date()) + "-" + netPackInfo.url))
    fileUrl.writeText(netPackInfo.toString())
    ... ...
}
HttpUrlConnectHook

編譯期對 HttpUrlConnection 進行 Hook ,底層網絡請求其實是代理到 OkhttpClient 上,這樣就能保證 HttpUrlConnection 所有網絡請求也能通過 OkHttp 進行處理,這樣流量攔截器( GlobalNetworkInterceptor )就可以進行復用了。

public object HttpUrlConnectHook {
    @JvmStatic
    fun proxy(httpUrlConnection: URLConnection): URLConnection {
        try {
            return hookOkHttpURLConnection(httpUrlConnection)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return urlConnection
    }
}


@Throws(Exception::class)
private fun hookOkHttpURLConnection(httpUrlConnection: URLConnection): URLConnection {
    val builder = OkHttpClient.Builder()
    val mClient = builder
            .retryOnConnectionFailure(true)
            ... ...
            .build()
    val strUrl = httpUrlConnection.url.toString()
    val url = URL(strUrl)
    val protocol = url.protocol.toLowerCase(Locale.ROOT)
    if (protocol.startWith("http", ignoreCase = true)) {
        return HttpUrlFactory.OkHttpURLConnection(url, mClient)
    } else urlConnection
}

2.2.2 分析詳情

通過展示網絡各個統計指標數據詳情,包括每個接口的請求流量大小、請求次數、請求內容,便於技術人員對流量問題進行分析。

2.3 頁面耗時

有贊零售面向B端的產品,適配了很多低端收銀機,在頁面流暢性有嚴格要求,通過頁面耗時監控,統計每個頁面( Activity | Fragment )的耗時,當頁面耗時超過閾值時,會生成問題,分配給相應的處理人進行修復。

2.3.1 原理

監控 Activity 與 Fragment onCreate() 方法開始執行作為頁面繪制開始時機,頁面 onDraw() 方法第一次回調時機作為頁面繪制結束時機,兩個時機做減法,算出頁面渲染耗時。

頁面監控開始時機

在 ActivityLifecycleCallbacks 全局監聽 Activity 生命周期,在 onActivityCreated() 方法中調用 watchActivity() 方法,watchActivity 除了統一對 Activity 頁面預埋開始時機外,還會區分 Activity 類型,對 Activity 內嵌的 Fragment 注冊 FragmentLifecycleCallbacks 監聽,同樣在 onFragmentViewCreate() 回調中對Fragment頁面開始時機進行預埋。

public void watchActivity(Activity activity) {
    watchWithMonitorView(activity.getClass().getName(), activity.getWindow().getDecorView()); 
    ... ...
    if (activity instanceof android.support.v4.app.FragmentActivity) {
            ((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks() {
                    public void onFragmentViewCreated(android.support.v4.app.FragmentManager fm, final android.support.v4.app.Fragment f, View v,
                                                      Bundle savedInstanceState) {
                        watchWithMonitorView(f.getClass().getName(), v);
                    }


                }), true);
    }
    ... ...
}
頁面監控結束時機

監控頁面根布局 onDraw() 第一次回調,定為頁面繪制結束時機。

public void watchWithMonitorView(final String className, final View view) {
        final long startTime = System.currentTimeMillis();
        final WeakReference<View> viewWeakReference = new WeakReference<>(view);
        final ViewTreeObserver.OnDrawListener onDrawListener = new ViewTreeObserver.OnDrawListener() {
            Boolean first = true;
            @Override
            public void onDraw() {
                if (startTime != 0 && first && viewWeakReference.get() != null) {
                ... ...
            }
        };
        view.getViewTreeObserver().addOnDrawListener(onDrawListener);
    }

2.3.2 分析詳情

在設備自動化回歸過程中一個頁面會被多次調用,在線下監控環境中,只有一個頁面3次超過耗時閾值( 200ms )才會算成有效的頁面卡頓問題,防止硬件不穩定造成問題誤報。

三、后台問題分析

設備自動化回歸過程中產生的性能數據存在一定的波動性,后台需要對批量性能數據進行算法校驗,評估出合理的有效問題,減少問題誤報。

分析工作流程(階段數據分析舉例):

設備自動化回歸過程采集到階段數據后,上傳到移動網關,晚上7點啟動定時任務,在后台拉取當前應用版本各個階段最近n條數據,進行數據聚合分析,計算出合理的階段耗時平均值,再同樣拉取當前應用上個版本各個階段的最近n條數據,同樣算出階段耗時平均值,與當前版本階段耗時平均值進行對比,算出漲跌幅度,如果超出閾值就會當成有效問題,進行分配與告警。

四、線下AWACS工具

在 QA 與開發過程中, App 上會懸浮告警 ICON ,開發者可以點擊告警 ICON 打開性能監控中心進行數據查看。性能監控中心會展示階段、 ANR 、慢方法、流量、 FPS 等性能數據,便於開發對問題進行排查。

五、問題管理與分配平台

后台對問題進行分析后,如果是有效問題會落到后台 db 中,前台在 mPaaS 搭建一套問題查看與分配 UI 看板,方便業務方對問題進行處理與狀態跟進。

5.1 問題列表

APM 監控的所有性能指標都可以在性能面板中進行切換篩選, tab 選中后再結合應用、狀態、環境篩選器列出問題列表。

5.2 問題詳情

點擊問題列表后跳轉到問題詳情,問題詳情中包含問題發生次數、進度、詳細信息、設備基本信息等,協助開發定位問題。

點擊問題詳情右上角“變更狀態”按鈕,可以變更問題狀態,並可選擇負責人對問題進行分配與跟進。

六、未來規划

1:監控更多維度的數據,包括 cpu 、線程、子線程更新 UI 等。

2:增加更多的自動化測試用例,針對特定的性能場景進行獨立測試(現在測試用例基本上都是主流程,覆蓋度還不太夠)。

3:補齊自動化設備的數量與型號,通過多機型綜合分析性能問題。

4:推廣到公司內部使用,協助解決有贊其他應用端性能問題。

end



‍‍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM