摘要: 本文整理自 OSC 第 55 期廣州源創(chuàng)會上江賽老師的演講忌卤,他詳細地介紹了移動端 APM 產(chǎn)品底層技術(shù)細節(jié)與實現(xiàn)方法扫夜。
江賽,聽云研發(fā)總監(jiān)驰徊,負責(zé)聽云移動端產(chǎn)品的研發(fā)工作笤闯。在 OSC 第 55 期廣州源創(chuàng)會上發(fā)表了題為《移動端 APM 產(chǎn)品研發(fā)技能》的演講。現(xiàn)場介紹移動端 APM 產(chǎn)品底層技術(shù)細節(jié)與實現(xiàn)方法棍厂, 演示如何通過在代碼中埋點來解決移動 APP 的性能問題 颗味;分享在實際產(chǎn)品開發(fā)中碰到的問題和一些經(jīng)驗,以及一些技術(shù)細節(jié)牺弹。
一浦马、移動 APM 概況
移動端 APM 產(chǎn)品,從字面上來理解例驹,APM(application performance monitor)就是應(yīng)用性能相關(guān)的監(jiān)測捐韩,可隨著現(xiàn)在產(chǎn)品的邊界越來越模糊,監(jiān)測的范圍不僅包括 performance鹃锈,還包括用戶行為荤胁,以及在穩(wěn)定性、卡頓屎债、崩潰這些方面的數(shù)據(jù)都有監(jiān)測仅政,已經(jīng)遠遠超過 performance 這一個角度,畢竟產(chǎn)品結(jié)構(gòu)越來越大了盆驹。
所以對于這樣一個產(chǎn)品圆丹,要做數(shù)據(jù)監(jiān)控和數(shù)據(jù)分析,它的基本前提是什么呢躯喇?就是必須要采集大齡的數(shù)據(jù)辫封,包括一些基本的數(shù)據(jù)硝枉。將這些數(shù)據(jù)放在不同的維度分析。
舉個例子倦微,從網(wǎng)絡(luò)的角度來說妻味,有用戶反饋某個產(chǎn)品在某個運營商范圍接入的情況下,網(wǎng)絡(luò)性能很差欣福。這個數(shù)據(jù)就會直接從報表里面去體現(xiàn)责球,因為會采集到一些基本的網(wǎng)絡(luò)數(shù)據(jù),也會采集到其他的不同的維護數(shù)據(jù)拓劝,然后這些問題就會展現(xiàn)出來雏逾。
從這張圖來看,數(shù)據(jù)是我們產(chǎn)品的一個移動研究方向郑临,而且我們的產(chǎn)品會支持蘋果栖博、Android 還有 Web 這三端。會采集的數(shù)據(jù)包括:網(wǎng)絡(luò)數(shù)據(jù)牧抵、交互行為數(shù)據(jù)笛匙、穩(wěn)定性相關(guān)數(shù)據(jù)和一些其他的數(shù)據(jù)(例如采集手機的信號。這些數(shù)據(jù)會有一些不同的應(yīng)用犀变,比如說運營商妹孙,它在部署各種基站的時候,會有一個參考值获枝,就是哪個地方信號不太好蠢正,它會在那里部署基站,但是怎樣知道信號不好呢省店?不可能在每一個角落都放一臺手機看信號如何嚣崭。此時我們的產(chǎn)品就可以完成這個任務(wù),移動端可以采集到這些信號懦傍,然后根據(jù)不同的地域來分析手機信號分布情況)雹舀,這就是采集數(shù)據(jù)的大概內(nèi)容。
然后往下細分會有更多類別粗俱。例如網(wǎng)絡(luò)數(shù)據(jù)说榆,從應(yīng)用層的數(shù)據(jù)來看,主要是采集 HTTP/HTTPS 的數(shù)據(jù)寸认,但又不僅僅是 HTTP/HTTPS 數(shù)據(jù)签财,比如說一條 HTTP 請求,假如從 Web 上或者是瀏覽器中輸入一個網(wǎng)址偏塞,我們會把所有的 HTTP 請求內(nèi)容分析出來唱蒸,例如出去包的長度、回來包的長度和 response 的時間等等灸叼。如果出現(xiàn)錯誤的時候神汹,還會把 response 的包和頭部信息打印出來庆捺,會把 HTTP 協(xié)議請求全部分析一遍,分析字節(jié)大小慎冤,響應(yīng)時間疼燥,還有錯誤這些情況。然后還會往下分析蚁堤,比如 HTTP 請求訪問之前需要做 TCP 鏈接的所用時間。
這些數(shù)據(jù)正常情況下是沒有辦法采集的但狭,需要特定的技術(shù)披诗,這個也是今天我要分享的內(nèi)容 —— 我們是如何抓取這些底層數(shù)據(jù)的。
還有一個是頁面加載的數(shù)據(jù)立磁,頁面的加載包含三種數(shù)據(jù)(頁面加載呈队、瀏覽器渲染和 DOM 加載)。Android 和 iOS 會通過 JS 注入監(jiān)控一些數(shù)據(jù)唱歧,和監(jiān)測一些頁面加載的詳細數(shù)據(jù)宪摧。
關(guān)于交互行為數(shù)據(jù),舉個例子颅崩,產(chǎn)品會監(jiān)控用戶在一個應(yīng)用里的一些點擊行為几于,像一系列的滑動,對菜單的選中沿后。比如說點擊一個按鈕以后沿彭,如果它的響應(yīng)時間過長,一般閾值是 3 秒鐘尖滚,如果點擊完按鈕 3 秒后才處理完喉刘,我們會自動把事件抓取并上報。現(xiàn)在我們還可以做到漆弄,當(dāng)監(jiān)測到卡頓以后渣慕,會自動去把當(dāng)前的操作截屏(可以做一秒鐘 10 幀的截屏)。通過一秒鐘 10 幀的數(shù)據(jù)而生成的動畫态坦,也就能看到卡頓的時候所在的頁面音婶。這個產(chǎn)品暫時還沒發(fā)布,但技術(shù)上已經(jīng)實現(xiàn)了∪耄現(xiàn)在關(guān)鍵的問題是普通的截屏?xí)浅S绊懶阅芎秃碾婘薮撸F(xiàn)在能做到 1 幀數(shù)據(jù)在 5 毫秒左右,效率非常高恨锚,截屏速度也非秤罴荩快。
關(guān)于穩(wěn)定性猴伶,穩(wěn)定性就是崩潰和 ANR(卡頓)相關(guān)的课舍。有一些開源項目可以支持這種需求塌西,所以類似崩潰、ANR 這些數(shù)據(jù)的采集難度不大筝尾。
收集了不同的源數(shù)據(jù)以后捡需,就會接觸到不同的維度,這些維度包括地域筹淫、運營商站辉、接入方式、設(shè)備损姜、操作系統(tǒng)饰剥、應(yīng)用版本以及其他一些維度數(shù)據(jù)。根據(jù)這些維度數(shù)據(jù)和一些自定義的相關(guān)信息摧阅,會做特定的網(wǎng)絡(luò)數(shù)據(jù)監(jiān)控汰蓉。通過這個,就可以看到對應(yīng)的不同源數(shù)據(jù)在不同的維度組合下的結(jié)果棒卷,比如可以選擇某一個地方顾孽、某一個運營商或者某個設(shè)備在某種接入方式上,它的 HTTP 請求效率比规,這就是基本源數(shù)據(jù)以及基本數(shù)據(jù)的應(yīng)用若厚。
很多應(yīng)用廠商也嘗試自己抓取這些龐大的數(shù)據(jù),但如果用傳統(tǒng)的方式來做苞俘,就意味著需要打很多的點盹沈,比如說一段代碼,需要在 excute 進入的地方打一個點吃谣,出去的地方也打一個點乞封,同時還要把參數(shù)抓取下來做參數(shù)的解析,這就意味著如果手工來做這種工作岗憋,工作量會非常大肃晚,因為所有監(jiān)控的地方都要埋點,而且一旦這段代碼發(fā)生變化仔戈,也就要重新去修改埋點的代碼关串,而且重新去埋點,也會導(dǎo)致工作量非常大监徘。
因此做數(shù)據(jù)采集的時候晋修,我們有一個基本原則:盡量不讓程序員做任何事情。添加一行初始化代碼就夠了凰盔。那么如何采集到這些數(shù)據(jù)墓卦?這就是數(shù)據(jù)采集的基礎(chǔ),自動埋點技術(shù)户敬。這些埋點的操作不需要自己做落剪,會通過程序自動完成睁本。下面介紹幾種自動埋點的方法。
二忠怖、APM 實現(xiàn) —— 自動埋點技術(shù)的介紹
主要通過以下的技術(shù)手段實現(xiàn):
下面對每一個技術(shù)細節(jié)展開進行講述:
對于 ByteCode 的處理呢堰,支持 Java ByteCode 的注入以及 Dalvik ByteCode 的注入。在內(nèi)應(yīng)用層會提供 Hook 方法來 Hook 分析 C/C++ 代碼凡泣,JavaScript 相關(guān)的會通過 JS 注入的方式來采集數(shù)據(jù)枉疼。
看起來比較抽象,下面一一展開來描述:
1. 從 Java 源代碼到 Dalvik Bytecode
對于 Android 程序員來說鞋拟,大部分代碼都是用 Java 寫的往衷,拓展名是 .java 的文件。但真正打包編譯完以后严卖,會生成 apk 文件。如果你把它解壓會看到有一個 dex 文件布轿,因為現(xiàn)在的包越來越大了哮笆,可能會有多個 dex 文件,那么這些 .java 文件是怎么變成 dex 文件的汰扭,這個過程是如何的稠肘?
編譯的過程是首先從 .java 文件到 class 文件,然后 class 文件再到 dex 文件萝毛。.java 文件到 class 文件是通過 javac 編譯项阴,然后再通過 Android SDK 下的一個工具 dx 將 class 文件編譯成 dex 文件。
在 Android 的虛擬機里面笆包,正常情況下編譯完以后环揽,Java 虛擬機里面執(zhí)行的是 .class 文件(即 Java Bytecode),但是在 Android 的 Dalvik 虛擬機或者 ART 里庵佣,不能直接執(zhí)行 Java Bytecode歉胶,因此需要將 Java Bytecode 做一次轉(zhuǎn)換,轉(zhuǎn)成 Dalvik Bytecode巴粪。該過程就是使用 dx 這個工具轉(zhuǎn)換的通今,而且是在編譯的時候完成。其實就是不同的格式表述肛根,.class 文件只是用了另外一種字節(jié)碼的格式來表述辫塌。這個東西看似很簡單,但如果了解編譯的過程派哲,就可以做很多的事情臼氨。 class 文件生成了以后,還沒有轉(zhuǎn)成 dex 文件這一步狮辽,就可以通過 ASM 技術(shù)一也,對 Java Bytecode 進行改寫巢寡,從而插入要監(jiān)控的代碼。
下面通過一個實際的例子來講述椰苟。
先來看代碼:
Example Java source: Foo.javaclass Foo { public static void main(String[] args) { System.out.println("Hello, world"); } public int method(int i1, int i2) { int i3 = i1 * i2; return i3 * 2; }}
這段代碼的功能很簡單抑月,里面有一個方法,傳進來兩個參數(shù)舆蝴,先將這兩個參數(shù)相乘谦絮,再把結(jié)果除以 2 返回。通過 javac 把它編譯成 Java Bytecode洁仗,然后用 javap 可以看到 Java Bytecode 的指令层皱。這是一個很簡單的 Java Bytecode 指令,取得兩個參數(shù)赠潦,然后做乘積叫胖。imul 指令就是 Java Bytecode 的一個基本指令,之后就是把兩個參數(shù)壓棧她奥,imul 指令會 pop 出棧底的兩個數(shù)瓮增。
$ javac Foo.java$ javap -v Foo public int method(int, int); flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=3 0: iload_1 1: iload_2 2: imul 3: istore_3 4: iload_3 5: iconst_2 6: imul 7: ireturn LineNumberTable: line 6: 0 line 7: 4
可以看到,方法的名字和參數(shù)都沒變哩俭。其實 Java Bytecode 和 Dalvik Bytecode 很大的一個區(qū)別就在這里绷跑,Java Bytecode 需要借助堆棧來模擬這種操作(乘法、除法)凡资,通過棧來臨時存放這些變量砸捏,但在 Dalvik Bytecode 里就不是通過棧來實現(xiàn),而是通過寄存器實現(xiàn)隙赁】巡兀看一個棧的操作示例:
StackBefore After value1 result value2 ... ... ... (imul指令對棧的操作)
先是傳入兩個變量 value1 和 value2,imul 執(zhí)行完以后就把結(jié)果加到棧里邊鸳谜,這就是一個典型的棧操作膝藕。
因為 Java Bytecode 沒有辦法在安卓手機上運行,因此需要將 Java Bytecode 繼續(xù)通過 dx 工具把它編譯成 Dalvik Bytecode咐扭。很多時候大家都是通過編譯工具進行編譯芭挽,沒有嘗試通過手工進行編譯,建議可以嘗試一下蝗肪。通過 dx 就可以把 class 文件編譯成一個 dex 文件袜爪,然后通過 dexdump 命令,把 dex 文件 dump 出來薛闪⌒凉荩可以看到,剛才的 Java Bytecode 里幾行乘法指令,在這就就變成了一行指令昙篙。
可以看到腊状,首先指令長度變小了,第二 Dalvike Bytecode 引入了寄存器的概念苔可。而 Java Bytecode 的函數(shù)調(diào)用全部是通過棧來模擬的缴挖。這種方式對代碼性能,以及代碼結(jié)構(gòu)大小有影響焚辅,而且寄存器本身的性能要比棧高很多映屋。
再看一下,剛剛那三行代碼兩次 pop 操作同蜻,一次乘積棚点,一次 push 操作,現(xiàn)在變成這樣一個操作湾蔓。就是這個指令瘫析,經(jīng)過目標(biāo)計算器,源計算器默责,操作完以后颁股,存在源計算機,現(xiàn)在變成這種形式傻丝。
下面來看一下 Java Bytecode 與 Dalvik Bytecode 的對比:
Java Bytecode 和 Dalvik Bytecode 有什么區(qū)別?前者用的是棧诉儒,后者用的是寄存器葡缰。
這些對于自動插碼技術(shù)有什么作用?前面提到的指令級插碼又有什么作用忱反?其實這些是基本工作泛释,首先要對Java Bytecode 非常的熟悉,之后要了解整個編譯過程温算。
這個代碼就是通過動作分析 Java Bytecode 注入的怜校,反編譯出來就是這樣。我們需要分析一些關(guān)鍵的方法注竿,還有特定方法茄茁,找到函數(shù)的頭和尾,插入需要的代碼巩割,第一步為獲取開始時間裙顽;第二,獲取完成的時間宣谈,之后進行上報愈犹。像做一些錯誤處理,會對異常進行捕捉闻丑,這樣就可以自動分析你的 Bytecode 來做注入漩怎。
還有一個特殊的情況勋颖,就是需要監(jiān)控的是這個調(diào)用,或者說監(jiān)控這個調(diào)用的反饋值勋锤,這些情況都會出現(xiàn)饭玲。但所有的變化都是基于對 Bytecode 上下文的理解,然后插入對應(yīng)的指令怪得。這個技術(shù)不是我們獨創(chuàng)的咱枉,ASM 技術(shù)已經(jīng)有很多年了,各位可以去看一些開源的 ASM 項目徒恋。
還有一個技術(shù)蚕断,Java Bytecode 注入是我們產(chǎn)品現(xiàn)在主要的注入方法,但是也還有很多其他注入的方法入挣,下面要講的就是另外一種的方式 —— 通過 .smali 注入亿乳,具體的邏輯如下圖所示:
通過一些 smali 反編譯工具,轉(zhuǎn)成 smali 文件径筏,靜態(tài)分析這些文件葛假,分析完以后會做代碼的注入,然后重新打包滋恬,再加一個簽名就可以了聊训。smali 不是 Android 官方的 Bytecode,是一個開源的 Bytecode恢氯。
這些大家都不陌生带斑,做 APP 開發(fā)很多時候會用這些工具幫助分析一些事情。同樣你也可以借鑒一些新的思路勋拟,通過這種方式分析 APK勋磕。認為存在惡意行為就分析。另外還可以做動態(tài)調(diào)試敢靡,把一些參數(shù)打印出來挂滓。
比如說寫了一個工程,可以做一個定制啸胧,寫一個簡單的SDK赶站。分析一個 APP 的時候,需要分析其網(wǎng)絡(luò)行為纺念,就把 SDK 注入進去亲怠,然后打包,之后看網(wǎng)絡(luò)訪問過程當(dāng)中訪問的什么主機柠辞、IP团秽。如果有加密,那就通過另外一個話題對流做解密,一般的情況下习勤,傳輸?shù)臄?shù)據(jù)都可以看到踪栋。
2. APM 實現(xiàn) —— native inline hook
因為 Android 中很多代碼不一定是用 Java 寫的,也可以用 C/C++ 寫图毕。這種代碼不能通過 Bytecode 的方式來注入夷都。看下面這張圖
這是一個普通的調(diào)用關(guān)系予颤,調(diào)用者調(diào)用被調(diào)用者執(zhí)行囤官,執(zhí)行完以后返回。這是正常的處理流程蛤虐。但如果要監(jiān)測這個被調(diào)用的方法党饮,想要拿到參數(shù),以及這個方法執(zhí)行多長時間驳庭,還想知道這個返回值刑顺,如何實現(xiàn)?邏輯上很簡單饲常,把被調(diào)用方法頭幾行指令做修改蹲堂。把指令改成 JMP 指令,JMP 到這個監(jiān)控方法里面贝淤,通過 hook 的方式做跳轉(zhuǎn)柒竞。這里做參數(shù)、相關(guān)函數(shù)的記錄播聪,做完以后再重新按照這個軌跡返回能犯。
如何做到這一步呢?首先犬耻,把頭幾行做跳轉(zhuǎn)。這需要對 ARM 指令执泰,對各種架構(gòu)比較熟悉才能做到枕磁。大部分程序員都學(xué)過匯編指令,但遇到的時候覺得很復(fù)雜术吝。實際上并不復(fù)雜计济,只是接觸的少,其實 ARM 32 指令不多排苍。根據(jù)后面 3 位沦寂,4 位可以做區(qū)分。還有一些分值指令淘衙,數(shù)學(xué)預(yù)算指令传藏。那么,分析這些指令的時候,首先對于指令架構(gòu)要很熟悉毯侦,而且哭靖,要知道源計算機,目標(biāo)計算器在哪里侈离。比如說试幽,最終跳轉(zhuǎn)指令的時候,要知道跳轉(zhuǎn)怎么計算卦碾,24 位 offset 怎么跳轉(zhuǎn)铺坞,24 位怎么轉(zhuǎn)換為絕對地址。如果把基本概念弄明白洲胖,不要求會寫济榨,就可以做下面的事情了。
先看一下剛剛說的方法怎么做到的宾濒。
需要改寫這個方法的頭兩行指令腿短,頭兩行指令替換成這樣的指令。PC 指令就是當(dāng)前運行時的邏輯地址绘梦,PC 寄存器橘忱。因為 ARM 32 會做一個預(yù)加載,這個會指向下兩行指令卸奉。如果將 PC 指令減 4钝诚,就是變?yōu)?PC 加 4,這個操作是把下一行指令移到 PC 寄存器中榄棵。如果改寫 PC 寄存器就實現(xiàn)了跳轉(zhuǎn)凝颇,雖然只有兩行代碼,但是可以想到這其實要花很長的時間疹鳄。
這需要了解 ARM 指令拧略,知道這個 ARM 指令執(zhí)行的過程,還要知道通過修改 PC 指令實現(xiàn)跳轉(zhuǎn)瘪弓。通過改寫頭兩行指令垫蛆,就可以把它跳轉(zhuǎn)到任何地址。而且這個地址就是 4 字節(jié)腺怯,32 位袱饭,4G 空間∏赫迹可以跳轉(zhuǎn)到任何函數(shù)虑乖,但這還沒結(jié)束。后兩行做了以后晾虑,要把頭兩行移到另外一個地方疹味。但是仅叫,移動指令的時候因為一些指令本身就是依賴 PC 指令,所以要去做指令的修復(fù)佛猛。因此更多的工作其實就是在修復(fù)這些被移走的指令惑芭。下面的例子是一個 B 指令修改,是寫實際代碼的一部分继找。
來看一下這一行代碼是什么意思遂跟。123,2 個 0 是 8 位婴渡,8123幻锁,高位是 0,0边臼,F(xiàn)哄尔。如果是 31 到 32 位,我們現(xiàn)在取的值是實際上就是取這 4 位柠并,1234岭接,取 4 位的值,通過這一行指令取這個值臼予。然后通過 4 個值區(qū)分這些指令類型鸣戴。取出來了以后,如果這個是 A粘拾,可以看一下 B 指令的方式窄锅,1010,這個是 1010缰雇,一個是1入偷,就是 BR 指令,跳出去再跳回來械哟。如果無條件這里就是 0疏之,1010 正好是多少就是 10,就是 A暇咆,如果是 B指令锋爪。B 指令跳轉(zhuǎn)依賴寄存器,首先算出來這個地址糯崎,把絕對地址存在這里,頭一行指令在這里河泳。
如果要真正把這個弄明白沃呢,可以通過編寫 C 代碼做到。如果做到這樣覺得很有成就感拆挥,把系統(tǒng)的 malloc薄霜,或者是 new 給 hook 住某抓,可以監(jiān)測所有的 native 內(nèi)存申請和釋放。
將 hook 技術(shù)應(yīng)用在產(chǎn)品上面惰瓜,發(fā)現(xiàn)很多的產(chǎn)品都是依賴這個技術(shù)的否副。比如安全方面,很多產(chǎn)品也是通過這種方式做的崎坊。還有通過這種方式來做一些底層資源修改和調(diào)度备禀,這個可以用在很多的方面。因為技術(shù)是為了產(chǎn)品服務(wù)的奈揍,只要把技術(shù)弄明白就可以了曲尸,最終還是會產(chǎn)品化。這是像我這種做很多年技術(shù)的人切身的體會男翰。有時候也是會沉迷在技術(shù)里面另患,總覺得做一些產(chǎn)品的工作就是浪費時間。現(xiàn)在想想蛾绎,并不如此昆箕。
最后一點,前面講的這些租冠,都是一些自動嵌碼技術(shù)鹏倘,包括 Java 應(yīng)用,還有 C++ 應(yīng)用肺稀。數(shù)據(jù)都是自動采集的第股。在編譯時插碼,在運行時使用 hook话原,這些都可以做夕吻,因為產(chǎn)品已經(jīng)很成熟。聽云現(xiàn)在運行著 5 億終端繁仁,有一些大的電商類也已經(jīng)在用聽云的 SDK涉馅。
舉個例子,想通過聽云對 TCP 層的監(jiān)測結(jié)果來觀察負載均衡調(diào)度情況黄虱,同一個主機有一堆 IP稚矿,正常情況下是沒有辦法拿到這個結(jié)果的。我們不僅可以拿到 DNS 時間捻浦,還可以拿到 DNS 結(jié)果晤揣,真實 IP 是什么,通過這些情況可以看到負載均衡服務(wù)器朱灿,即調(diào)度出來的結(jié)果情況以及 IP 分布情況昧识,另外還有 TCP 三次握手時間,SSL 握手時間等盗扒。
這些數(shù)據(jù)都非常的有用跪楞。安卓程序員經(jīng)常糾結(jié)使用哪些網(wǎng)絡(luò)庫缀去,是 urlconnection,還是 okhttp甸祭。分別都有什么優(yōu)缺點缕碎。這個我們就給你們做了一個強大的技術(shù)驗證。
第一個問題池户,比如說咏雌,在程序里面連著發(fā)了 10 個 request。現(xiàn)在 HTTP 訪問的傳輸層都是基于 TCP煞檩,但每發(fā)一次 request 都要做一次 TCP 連接嗎处嫌?仔細想想,對于同一個地址肯定沒有必要斟湃,這樣做就是浪費時間熏迹。然后遇到的就是 TCP 復(fù)用技術(shù),通過這種技術(shù)凝赛,就可以監(jiān)測對于一個同一個目標(biāo)地址發(fā)生多少次 TCP connect 操作注暗,這就知道在這個訪問時間內(nèi)有沒有復(fù)用之前的連接。所以墓猎,就可以得出一個指標(biāo)數(shù)據(jù)捆昏,即發(fā)生了多少次 TCP 連接。
下圖是 APM 產(chǎn)品
通過這種技術(shù)可以監(jiān)測一些關(guān)鍵指標(biāo)數(shù)據(jù)毙沾,因為采取底層原數(shù)據(jù)骗卜,很多點就會把這個原數(shù)據(jù)還原出應(yīng)用場景,客戶想出來的場景比我們多左胞。這些原數(shù)據(jù)都是最寶貴的數(shù)據(jù)寇仓,并且最關(guān)鍵的是不需要你再去做額外的工作,也是 APM 的價值所在烤宙。
三遍烦、總結(jié)
今天講的內(nèi)容比較抽象,講的是研發(fā)過程中的一些經(jīng)驗躺枕,技巧和總結(jié)服猪。這個技術(shù)可能對各位現(xiàn)在的工作不會有直接的幫助,因為太底層拐云,但也希望可以給各位對自己工作的方式帶去一定的思考罢猪。無論怎樣,還是需要把底層的知識弄明白叉瘩,畢竟這對于寫代碼有幫助膳帕。
? 著作權(quán)歸作者所有
分類:OSC源創(chuàng)會
字?jǐn)?shù):5362