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

作者:洪恩濤

前言

一、架構設計

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

二、監控指標分析

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

2.1 階段數據

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

2.1.1 方法耗時分析

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

原理

分析詳情

應用所有版本產生的階段數據都會上傳到後端,後端會通過定式任務對階段數據進行分析,分析角度分爲新增、新減、陡增、陡降 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:推廣到公司內部使用,協助解決有贊其他應用端性能問題。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0Uf0G8P43XUWl7IPVtAu8g