《電子技術應用》
您所在的位置:首頁 > 模擬設計 > 業(yè)界動態(tài) > 睡覺的時候,程序能不能自動查 bug?

睡覺的時候,程序能不能自動查 bug?

2020-02-17
來源:搜狐科技
關鍵詞: Bug ChaosEngineering Google

e067552dc5b54730baa4006c3a4f4e8c.jpeg

  作者 | 杜沁園 等

  責編 | 郭芮

  出品 | CSDN(ID:CSDNnews)

  曾在 Hacker News 上看到過一個 Oracle 工程師處理 bug 的 日常:

  先花兩周左右時間來理解 20 個參數如何通過神奇的組合引發(fā) bug。

  改了幾行代碼,嘗試對 bug 進行修復,提交測試集群開始跑近百萬個測試 case,通常要 20~30 小時。

  運氣好的話會有 100 多個 case 沒過,有時候上千個也有可能,只好挑選幾個來看,發(fā)現還有 10 個參數之前沒有注意到。

  又過了兩周,終于找到了引起 bug 的真正參數組合,并跑通了所有測試。并增加 100 多個測試 case 確保覆蓋他的修改。

  經過一個多月的代碼 review,他的修改終于合并了,開始處理下一個 bug……

  后來這個工程師感慨說:“I don't work for Oracle anymore. Will never work for Oracle again!”

  Oracle 12.2 有將近 2500 萬行 C 代碼,復雜系統(tǒng)的測試是一件艱難、艱苦和艱巨的事情,而測試一個分布式數據庫的情況就更復雜了。我們永遠不知道用戶可能寫出什么樣的 SQL,表結構和索引有多少種組合,此外還要考慮集群在什么時候節(jié)點發(fā)生宕機,以及受到網絡抖動、磁盤性能退化等因素的影響——可能性幾乎是無限的。

  那么有沒有一種方法能讓程序自動幫我們查 bug?

  如何做到「睡覺的時候讓程序自動查 bug」?

  項目的思路其實很簡單,如果在每次跑 case 的時候能用統(tǒng)計學的方法對足夠多次實驗的代碼路徑進行分析,就可以找出疑似 bug 的代碼,最終結果以代碼染色的方式由前端可視化呈現,就得到了如下圖展示的效果:

2.gif

  「顏色越深,亮度越高」表示包含錯誤邏輯的可能性越大。該方法不僅適用于數據庫系統(tǒng)的測試,同樣適用于其他任何復雜的系統(tǒng)。

  背后的原理

  項目最初是受到 VLDB 的一篇論文的啟發(fā) APOLLO: Automatic Detection and Diagnosis of Performance Regressions in Database Systems,該論文主要圍繞如何診斷引發(fā)數據庫性能回退的代碼,其核心思想也同樣適用于排查 bug。論文中提到的自動診斷系統(tǒng)由 SQLFuzz,SQLMin 和 SQLDebug 三個模塊組成。

3.png

  SQLFuzz:負責隨機生成 SQL,并利用二分查找定位到性能回退的前后兩個版本,傳遞給 SQLMin 模塊。

  SQLMin:通過剪枝算法將 SQLFuzz 生成的 SQL 進行化簡,得出能夠復現該問題的最小 SQL ,傳遞給 SQLDebug 模塊。目的是減少無關的代碼路徑,降低噪音。

  SQLDebug:對源碼進行插樁,使其在執(zhí)行 SQL 時能夠輸出代碼的執(zhí)行路徑。然后對兩個版本的代碼路徑進行分析,建立一個統(tǒng)計模型來定位問題的位置。

  最終系統(tǒng)自動生成測試報告,內容包含:

  哪一次的代碼 commit 引入了性能回退。

  存在問題的代碼源文件。

  具體的函數位置。

  而實際上,考慮到并發(fā)、循環(huán)、遞歸等帶來的影響,代碼執(zhí)行路徑分析會非常復雜。為了保證能夠在 Hackathon 那么短的時間內展示出效果,我們又參考了另一篇論文 Visualization of Test Information to Assist Fault Localization,其核心思想是通過統(tǒng)計代碼塊被正確和錯誤測試用例經過次數,再基于分析算法來涂上不同的顏色,簡單而實用。

4.png

  其實借助這個思路也可以應用到其他領域,后面我們將展開來介紹。接下來我們先來看看 SQLDebug 是如何實現的。

  聊聊細 (gān) 節(jié) (huò)

  如何自動產生測試 case?

  由于是基于統(tǒng)計的診斷,我們需要先構建足夠多的測試用例,這個過程當然最好也由程序自動完成。事實上,grammar-based 的測試在檢驗編譯器正確性方面有相當長的歷史,DBMS 社區(qū)也采用類似的方法來驗證數據庫的功能性。比如:微軟的 SQL Server 團隊開發(fā)的 RAGS 系統(tǒng)對數據庫進行持續(xù)的自動化測試,還有社區(qū)比較出名的 SQLSmith 項目等等。今年 TiDB Hackathon 的另一個獲獎項目 sql-spider 也是實現類似的目的。

  這里我們暫時采用 PingCAP 開源的隨機測試框架 go-randgen 實現 SQL fuzzing,它需要用戶寫一些規(guī)則文件來幫助生成隨機的 SQL 測試用例。規(guī)則文件由一些產生式組成。randgen 每次從 query 開始隨機游走一遍產生式,生成一條 SQL,產生一條像下圖紅線這樣的路徑。

5.png

  我們將每個產生式生成正確與錯誤用例的比例作為該產生式的顏色值,繪制成一個頁面,作為 SQLFuzz 的展示頁面。通過該頁面,可以比較容易地看出哪條產生式更容易產生錯誤的 SQL。

6.gif

  代碼跟蹤

  為了跟蹤每一條 SQL 在運行時的代碼執(zhí)行路徑,一個關鍵操作是對被測程序進行插樁 (Dynamic Instrumentation)。VLDB 論文中提到一個二進制插樁工具 DynamoRIO,但是我們不確定用它來搞 Go 編譯的二進制能否正常工作。換一個思路,如果能在編譯之前直接對源碼進行插樁呢?

  參考 go cover tool 的實現,我們寫了一個專門的代碼插樁工具 tidb-wrapper。它能夠對任意版本的 TiDB 源碼進行處理,生成 wrapped 代碼。并且在程序中注入一個 HTTP Server,假設某條 SQL 的摘要是 df6bfbff(這里的摘要指的是 SQL 語句的 32 位 MurmurHash 計算結果的十六進制,主要目的是簡化傳輸的數據),那么只要訪問 http://<tidb-server-

7.png

ip>::43222/trace/df6bfbff 就能獲得該 SQL 所經過的源碼文件和代碼塊信息。

  因為主要目標是正確性診斷,所以我們限定系統(tǒng)不對 TiDB 并發(fā)執(zhí)行 SQL,這樣就可以認為從 server/conn.go:handleQuery 方法被調用開始,到 SQLDebug 模塊訪問 trace 接口的這段時間所有被執(zhí)行的基本塊都是這條 SQL 的執(zhí)行路徑。當 SQLDebug 模塊訪問 HTTP 接口,將會同時刪除該 SQL 相關的 trace 信息,避免內存被撐爆。

  基本塊統(tǒng)計

  SQLDebug 模塊在獲取到每條 SQL 經過的基本塊信息后,會對每個基本塊建立如下的可視化模型。

  首先是顏色,經過基本塊的失敗用例比例越高,基本塊的顏色就越深。

8.png

  然后是亮度,經過基本塊的失敗用例在總的失敗用例中占的比例越高,基本塊的亮度越高。

9.png

  已經有了顏色指標,為什么還要一個亮度指標呢?其實亮度指標是為了彌補“顏色指標 Score”的一些偏見。比如某個代碼路徑只被一個錯誤用例經過了,那么它顯然會獲得 Score 的最高分 1,事實上這條路徑不那么有代表性,因為這么多錯誤用例中只有一個經過了這條路徑,大概率不是錯誤的真正原因。所以需要額外的一個亮度指標來避免這種路徑的干擾, 只有顏色深,亮度高的代碼塊,才是真正值得懷疑的代碼塊。

  上面的兩個模型主要是依據之前提到的 Visualization 的論文,我們還自創(chuàng)了一個文件排序的指標,失敗用例在該文件中的密度越大(按照基本塊),文件排名越靠前:

10.png

  前端拿到這些指標后,按照上面計算出的文件排名順序進行展示,越靠前的文件存在問題的風險就越高。

11.png

  當點擊展開后可以看到染色后的代碼塊:

12.png

  我們經過一些簡單的實驗,文件級別的診斷相對比較準確,對于基本塊的診斷相對還有些粗糙,這跟沒有實現 SQLMin 有很大關系,畢竟 SQLMin 能去除不少統(tǒng)計時的噪聲。

  還能不能做點別的?

  看到這里,你可能覺得這個項目不過是針對數據庫系統(tǒng)的自動化測試。而實際上借助代碼自動調試的思路,可以給我們更多的啟發(fā)。

  源碼教學

  閱讀和分析復雜系統(tǒng)的源碼是個頭疼的事情,基于源碼的運行時可視化跟蹤能否做成一個通用工具呢?這樣在程序執(zhí)行的同時就可以直觀地看到代碼的運行過程,對快速理解源碼一定會大有幫助。更進一步,配合源碼在線執(zhí)行有沒有可能做成一個在線 web 應用呢?

  全鏈路測試覆蓋統(tǒng)計

  語言本身提供的單測覆蓋統(tǒng)計工具已經比較完備了,但一般測試流程中還要通過 e2e 測試、集成測試、穩(wěn)定性測試等等。能否用本文的方法綜合計算出各種測試的覆蓋度,并且與 CI 系統(tǒng)和自動化測試平臺整合起來。利用代碼染色技術,還可以輸出代碼執(zhí)行的熱力圖分析。結合 profiler 工具,是不是還可以輔助來定位代碼的性能問題?

  Chaos Engineering

  在 PingCAP 內部有諸多的 Chaos 測試平臺,用來驗證分布式系統(tǒng)的魯棒性,譬如像 Schrodinger,Jepsen 等等?;煦鐪y試有個弊端就是,當跑出問題之后想再次復現就很難,所以只能通過當時的情形去猜代碼可能哪里有問題。如果能在程序運行時記錄代碼的執(zhí)行路徑,根據問題發(fā)生時間點附近的日志和監(jiān)控進一步縮小范圍,再結合代碼路徑進行分析就能精確快速的定位到問題的原因。

  與分布式 Tracing 系統(tǒng)集成

  Google 有一篇論文是介紹其內部的 分布式追蹤系統(tǒng) Dapper ,同時社區(qū)也有比較出名的項目 Open Tracing 作為其開源實現,Apache 下面也有類似的項目 Skywalking。一般的 Tracing 系統(tǒng)主要是跟蹤用戶請求在多個服務之間的調用關系,并通過可視化來輔助排查問題。但是 Tracing 系統(tǒng)的跟蹤粒度一般是服務層面,如果我們把 trace_id 和 span_id 也當作標注傳遞給代碼塊進行打樁,那是不是可以在 Tracing 系統(tǒng)的界面上直接下鉆到源碼,聽起來是不是特別酷?

  接下來的工作

  以上我們只完成了一個非常簡單的原型,距離真正實現睡覺時程序自動查 bug 還有一段路要走,我們計劃對項目持續(xù)的進行完善。

  接下來,首先要支持并行執(zhí)行多個測試用例,這樣才能在短時間得到足夠多的實驗樣本,分析結果才能更加準確。另外,要將注入的代碼對程序性能的影響降低到最小,從而應用于更加廣泛的領域,比如性能壓測場景,甚至在生產環(huán)境中也能夠開啟。

  看到這里可能你已經按耐不住了,附上項目的完整源碼:

  https://github.com/fuzzdebugplatform/fuzz_debug_platform

  Welcome to hack!

  作者簡介:

  黃寶靈,PingCAP 前端開發(fā)工程師,喜歡 React 和 Type。

  滿俊朋, 效率工具工程師, 目前在 PingCAP 從事 Benchmark, Stability 相關工具的研發(fā)。

  杜沁園,中科大研究生,曾在 PingCAP 實習,從事數據庫測試工具的研發(fā)。

  韓玉博,中科大研究生,在 Tradeshift 實習,從事前端開發(fā)。


本站內容除特別聲明的原創(chuàng)文章之外,轉載內容只為傳遞更多信息,并不代表本網站贊同其觀點。轉載的所有的文章、圖片、音/視頻文件等資料的版權歸版權所有權人所有。本站采用的非本站原創(chuàng)文章及圖片等內容無法一一聯系確認版權者。如涉及作品內容、版權和其它問題,請及時通過電子郵件或電話通知我們,以便迅速采取適當措施,避免給雙方造成不必要的經濟損失。聯系電話:010-82306118;郵箱:aet@chinaaet.com。