曾在 Hacker News 上看到過一個 Oracle 工程師處理 bug 的 平常:前端
後來這個工程師感慨說:「I don't work for Oracle anymore. Will never work for Oracle again!」git
Oracle 12.2 有將近 2500 萬行 C 代碼,複雜系統的測試是一件艱難、艱苦和艱鉅的事情。而測試一個分佈式數據庫的狀況就更復雜了,咱們永遠不知道用戶可能寫出什麼樣的 SQL,表結構和索引有多少種組合,此外還要考慮集羣在何時節點發生宕機,以及受到網絡抖動、磁盤性能退化等因素的影響,可能性幾乎是無限的。github
那麼能不能寫個機器人自動幫咱們查 bug 呢?golang
這彷佛是個不錯的主意,帶着這個想法咱們組了團隊,來參加 TiDB Hackathon 2019 比賽,並意外地斬獲了三等獎。web
項目的思路其實很簡單,若是在跑測試 case 的時候可以用統計學的方法對足夠屢次實驗的代碼路徑進行分析,就能夠找出疑似 bug 的代碼,最終結果以代碼染色的方式由前端可視化呈現,就獲得了以下圖展現的效果:算法
這是咱們在 Hackathon 比賽中針對一個 TiDB 的 PR 所作的實驗,顏色越深,亮度越高表示包含錯誤邏輯的可能性越大。該方法不只適用於數據庫系統的測試,一樣適用於其餘任何複雜的系統。sql
項目最初是受到 VLDB 的一篇論文的啓發 APOLLO: Automatic Detection and Diagnosis of Performance Regressions in Database Systems,在此感謝一下喬治亞理工學院和 eBay 公司的幾位做者。該論文主要圍繞如何診斷引起數據庫性能回退的代碼,其核心思想也一樣適用於排查 bug。論文中提到的自動診斷系統由 SQLFuzz,SQLMin 和 SQLDebug 三個模塊組成。數據庫
最終系統自動生成測試報告,內容包含:apache
而實際上,考慮到併發、循環、遞歸等帶來的影響,代碼執行路徑分析會很是複雜。爲了保證可以在 Hackathon 那麼短的時間內展現出效果,咱們又參考了另外一篇論文 Visualization of Test Information to Assist Fault Localization,其核心思想是經過統計代碼塊被正確和錯誤測試用例通過次數,再基於分析算法來塗上不一樣的顏色,簡單而實用。數組
其實藉助這個思路也能夠應用到其餘領域,後面咱們將展開來介紹。接下來咱們先來看看 SQLDebug 是如何實現的。
因爲是基於統計的診斷,咱們須要先構建足夠多的測試用例,這個過程固然最好也由程序自動完成。事實上,grammar-based 的測試在檢驗編譯器正確性方面有至關長的歷史,DBMS 社區也採用相似的方法來驗證數據庫的功能性。好比:微軟的 SQL Server 團隊開發的 RAGS 系統對數據庫進行持續的自動化測試,還有社區比較出名的 SQLSmith 項目等等。今年 TiDB Hackathon 的另外一個獲獎項目 sql-spider 也是實現相似的目的。
這裏咱們暫時採用 PingCAP 開源的隨機測試框架 go-randgen 實現 SQL fuzzing,它須要用戶寫一些規則文件來幫助生成隨機的 SQL 測試用例。規則文件由一些 SQL 語法表達式組成。randgen 每次從 query 開始隨機遊走一遍 SQL 表達式的語法樹,生成一條 SQL,產生一條像下圖紅線這樣的路徑。
咱們將每一個語法表達式生成正確與錯誤用例的比例做爲它的顏色值,繪製出 SQLFuzz 的展現頁面。經過該頁面,就能夠比較容易地看出哪條表達式更容易產生錯誤的 SQL。
爲了跟蹤每一條 SQL 在運行時的代碼執行路徑,一個關鍵操做是對被測程序進行插樁 (Dynamic Instrumentation)。VLDB 論文中提到一個二進制插樁工具 DynamoRIO,可是咱們用了另一個思路,爲何不在編譯以前直接對源碼進行插樁呢?
參考 go cover tool 的實現,咱們寫了一個專門的代碼插樁工具 tidb-wrapper。它可以對任意 TiDB 源碼進行處理,生成一份 wrapped 代碼,而且在程序中注入一個 HTTP Server,假設某條 SQL 的摘要是 df6bfbff
(這裏的摘要指的是 SQL 語句的 32 位 MurmurHash 計算結果的十六進制,主要目的是簡化傳輸的數據),那麼只要訪問 http://<tidb-server-ip>::43222/trace/df6bfbff
就能得到該 SQL 所通過的源碼文件和代碼塊信息。
// http://localhost:43222/trace/df6bfbff { "sql": "show databases", "trace": [ { "file": "executor/batch_checker.go", "line": null }, { "file": "infoschema/infoschema.go", "line": [ [ 113, 113 ], [ 261, 261 ], //.... } ], }
line 字段輸出的每一個二元組都是一個基本塊的起始與結束行號(左閉右閉)。基本塊的定義是絕對不會產生分支的一個代碼塊,也是咱們統計的最小粒度。那是如何識別出 Go 代碼中基本塊的呢?其實工做量還挺大的,幸虧 Go 的源碼中有這一段,咱們又恰好看到過,就把它裁剪出來,成爲 go-blockscanner。
由於主要目標是正確性診斷,因此咱們限定系統不對 TiDB 併發執行 SQL,這樣就能夠認爲從 server/conn.go:handleQuery
方法被調用開始,到 SQLDebug 模塊訪問 trace 接口的這段時間全部被執行的基本塊都是這條 SQL 的執行路徑。當 SQLDebug 模塊訪問 HTTP 接口,將會同時刪除該 SQL 相關的 trace 信息,避免內存被撐爆。
SQLDebug 模塊在獲取到每條 SQL 通過的基本塊信息後,會對每一個基本塊創建以下的可視化模型。
首先是顏色,通過基本塊的失敗用例比例越高,基本塊的顏色就越深。
而後是亮度,通過基本塊的失敗用例在總的失敗用例中佔的比例越高,基本塊的亮度越高。
已經有了顏色指標,爲何還要一個亮度指標呢?其實亮度指標是爲了彌補「顏色指標 Score」的一些偏見。好比某個代碼路徑只被一個錯誤用例通過了,那麼它顯然會得到 Score 的最高分 1,事實上這條路徑不那麼有表明性,由於這麼多錯誤用例中只有一個通過了這條路徑,大機率不是錯誤的真正緣由。因此須要額外的一個亮度指標來避免這種路徑的干擾,只有顏色深,亮度高的代碼塊,纔是真正值得懷疑的代碼塊。
上面的兩個模型主要是依據以前提到的 Visualization 的論文,咱們還自創了一個文件排序的指標,失敗用例在該文件中的密度越大(按照基本塊),文件排名越靠前:
前端拿到這些指標後,按照上面計算出的文件排名順序進行展現,越靠前的文件存在問題的風險就越高。
當點擊展開後能夠看到染色後的代碼塊:
咱們通過一些簡單的實驗,文件級別的診斷相對比較準確,對於基本塊的診斷相對還有些粗糙,這跟沒有實現 SQLMin 有很大關係,畢竟 SQLMin 能去除很多統計時的噪聲。
看到這裏,你可能以爲這個項目不過是針對數據庫系統的自動化測試。而實際上藉助代碼自動調試的思路,能夠給咱們更多的啓發。
閱讀和分析複雜系統的源碼是個頭疼的事情,TiDB 就曾出過 24 篇源碼閱讀系列文章,用一篇篇文字爲你們解讀源碼,江湖人稱 「二十四章經」。那麼是否能夠基於源碼的可視化跟蹤作成一個通用工具呢?這樣在程序執行的同時就能夠直觀地看到代碼的運行過程,對快速理解源碼必定會大有幫助。更進一步,配合源碼在線執行有沒有可能作成一個在線 web 應用呢?
語言自己提供的單測覆蓋統計工具已經比較完備了,但通常測試流程中還要經過 e2e 測試、集成測試、穩定性測試等等。可否用本文的方法綜合計算出各類測試的覆蓋度,而且與 CI 系統和自動化測試平臺整合起來。利用代碼染色技術,還能夠輸出代碼執行的熱力圖分析。結合 profiler 工具,是否是還能夠輔助來定位代碼的性能問題?
在 PingCAP 內部有諸多的 Chaos 測試平臺,用來驗證分佈式系統的魯棒性,譬如像 Jepsen ,還有 PingCAP 自研的薛定諤穩定性測試系統等。混沌工程測試比較困擾的一點是,當跑出問題以後想再次復現就很難,只能經過當時的情形去猜代碼可能哪裏有問題。若是能在程序運行時記錄代碼的執行路徑,根據問題發生時間點附近的日誌和監控進一步縮小範圍,再結合代碼執行路徑分析就能精確快速的定位到問題的緣由。
Google 有一篇論文是介紹其內部的 分佈式追蹤系統 Dapper ,同時社區也有比較出名的項目 Open Tracing 做爲其開源實現,Apache 下面也有相似的項目 Skywalking。通常的 Tracing 系統主要是跟蹤用戶請求在多個服務之間的調用關係,並經過可視化來輔助排查問題。可是 Tracing 系統的跟蹤粒度通常是服務層面,若是咱們把 trace_id
和 span_id
也看成標註傳遞給代碼塊進行打樁,那是否是能夠在 Tracing 系統的界面上直接下鑽到源碼,聽起來是否是特別酷?
由於 Hackathon 時間有限,咱們當時只完成了一個很是簡單的原型,距離真正實現睡覺時程序自動查 bug 還有一段路要走,咱們計劃對項目持續的進行完善。
接下來,首先要支持並行執行多個測試用例,這樣才能在短期獲得足夠多的實驗樣本,分析結果才能更加準確。另外,要將注入的代碼對程序性能的影響下降到最小,從而應用於更加普遍的領域,好比性能壓測場景,甚至在生產環境中也可以開啓。
看到這裏可能你已經按耐不住了,附上 項目的完整源碼,Welcome to hack!
做者介紹:我和個人 SQL 隊(成員:杜沁園、韓玉博、黃寶靈、滿俊朋),他們的項目「基於路徑統計的 sql bug root cause 分析」得到了 TiDB Hackathon 2019 的三等獎。