特性介紹 | MySQL 測試框架 MTR 系列教學(二):進階篇

2023-05-08 06:01:13

作者:盧文雙 資深資料庫核心研發

序言:

以前對 MySQL 測試框架 MTR 的使用,主要集中於 SQL 正確性驗證。近期由於工作需要,深入瞭解了 MTR 的方方面面,發現 MTR 的能力不僅限於此,還支援單元測試、壓力測試、程式碼覆蓋率測試、記憶體錯誤檢測、執行緒競爭與死鎖等功能,因此,本著分享的精神,將其總結成一個系列。

主要內容如下:

  • 入門篇:工作機制、編譯安裝、引數、指令範例、推薦用法、新增 case、常見問題、異常偵錯
  • 進階篇:高階用法,包括單元測試、壓力測試、程式碼覆蓋率測試、記憶體錯誤檢測、執行緒競爭與死鎖
  • 原始碼篇:分析 MTR 的原始碼
  • 語法篇:單元測試、壓力測試、mysqltest 語法、異常偵錯

由於個人水平有限,所述難免有錯誤之處,望雅正。

本文是第二篇進階篇

本文首發於 2023-05-01 21:03:44


MTR 系列基於 MySQL 8.0.29 版本(編譯情況也在 8.0.32 版本驗證過),且主要在 Ubuntu 22.04 X86_64 驗證(部分指令也在 Ubuntu 20.04 X86_64、Ubuntu 22.04 ARM64、MacOS M1 做了驗證),如有例外,會特別說明。

前言

上一篇文章《MySQL 測試框架 MTR 系列教學(一):入門篇》介紹了 mtr 的原理、目錄結構、引數及常見用法,側重於最常見的 SQL 正確性驗證,但 mtr 能做更多的事情,比如 記憶體錯誤、執行緒競爭、程式碼覆蓋率、壓力測試等,本文主要介紹這些內容,涉及的相關工具如下:

  • valgrind :用於記憶體偵錯、記憶體漏失檢測以及效能分析。
  • Sanitizier :谷歌發起的開源工具集。
    • ASAN/AddressSanitizier :檢查記憶體地址相關問題,包括記憶體漏失、釋放後使用、重複釋放、堆溢位、棧溢位等等問題。
    • LSAN/LeakSanitizer :檢查記憶體漏失問題。它是整合在 Address Sanitizer 中的一個相對獨立的工具,它工作在檢查過程的最後階段。
    • MSAN/MemorySanitizer : 檢查使用未初始化記憶體的問題。
    • TSAN/ThreadSanitizer : 檢查執行緒資料競爭和死鎖問題。
    • UBSAN/UndefinedBehaviorSanitizer : 檢測未定義行為(使用空指標、有符號整數溢位等)。
  • gcov : 程式碼覆蓋率測試。
  • gprof : 效能分析工具。
  • 單元測試
  • 壓力測試

本文將逐一介紹對各個工具的支援情況。

補充:

MariaDB 已經很好的支援了以上工具集:

Compile and Using MariaDB with Sanitizers (ASAN, UBSAN, TSAN, MSAN) - MariaDB Knowledge Base

Compiling MariaDB for Debugging - MariaDB Knowledge Base (支援 valgrind)

MySQL 編譯選項

首先說明一下與本文相關的 MySQL 編譯選項:

  • -DCMAKE_BUILD_TYPE=type

    The type of build to produce:

    • RelWithDebInfo: default valueEnable optimizations and generate debugging information. This is the default MySQL build type.
    • Release: Enable optimizations but omit debugging information to reduce the build size. This build type was added in MySQL 8.0.13 (MySQL 5.7 is not supported).
    • Debug: Disable optimizations and generate debugging information. This build type is also used if the WITH_DEBUG option is enabled. That is, -DWITH_DEBUG=1 has the same effect as -DCMAKE_BUILD_TYPE=Debug.
  • -DWITH_DEBUG=bool

    Whether to include debugging support. The default isOFF.

  • -DWITH_ASAN=bool

    Whether to enable the AddressSanitizer, for compilers that support it. The default is off.

  • -DWITH_ASAN_SCOPE=bool

    Whether to enable the AddressSanitizer -fsanitize-address-use-after-scope Clang flag for use-after-scope detection. The default is off. To use this option, -DWITH_ASAN must also be enabled.

  • -DWITH_LSAN=bool

    Whether to run LeakSanitizer, without AddressSanitizer. The default isOFF.

    This option was added in MySQL 8.0.16.

  • -DWITH_MSAN=bool

    Whether to enable MemorySanitizer, for compilers that support it. The default is off.

    For this option to have an effect if enabled, all libraries linked to MySQL must also have been compiled with the option enabled.

  • -DWITH_TSAN=bool

    Whether to enable the ThreadSanitizer, for compilers that support it. The default is off.

  • -DWITH_UBSAN=bool

    Whether to enable the Undefined Behavior Sanitizer, for compilers that support it. The default is off.

  • -DWITH_UNIT_TESTS={ON|OFF}

    If enabled, compile MySQL with unit tests. The default is ON unless the server is not being compiled.

  • -DWITH_VALGRIND=bool

    Whether to compile in the Valgrind header files, which exposes the Valgrind API to MySQL code. The default isOFF.

    To generate a Valgrind-aware debug build, -DWITH_VALGRIND=1 normally is combined with -DWITH_DEBUG=1. See Building Debug Configurations.

  • -DENABLE_GCOV=bool

    Whether to include gcov support (Linux only).

  • -DENABLE_GPROF=bool

    Whether to enable gprof (optimized Linux builds only). The default isOFF.

  • -DWITH_TEST_TRACE_PLUGIN=bool

    Whether to build the test protocol trace client plugin (see Using the Test Protocol Trace Plugin). By default, this option is disabled. Enabling this option has no effect unless the WITH_CLIENT_PROTOCOL_TRACING option is enabled. If MySQL is configured with both options enabled, the libmysqlclient client library is built with the test protocol trace plugin built in, and all the standard MySQL clients load the plugin. However, even when the test plugin is enabled, it has no effect by default. Control over the plugin is afforded using environment variables; see Using the Test Protocol Trace Plugin.

    Note

    Do not enable the WITH_TEST_TRACE_PLUGIN option if you want to use your own protocol trace plugins because only one such plugin can be loaded at a time and an error occurs for attempts to load a second one. If you have already built MySQL with the test protocol trace plugin enabled to see how it works, you must rebuild MySQL without it before you can use your own plugins.

    For information about writing trace plugins, see Writing Protocol Trace Plugins.

  • -DWITH_CLIENT_PROTOCOL_TRACING=bool

    Whether to build the client-side protocol tracing framework into the client library. By default, this option is enabled.

    For information about writing protocol trace client plugins, see Writing Protocol Trace Plugins.

    See also the WITH_TEST_TRACE_PLUGIN option.

  • -DWITH_KEYRING_TEST=bool

    Whether to build the test program that accompanies the keyring_file plugin. The default isOFF. Test file source code is located in the plugin/keyring/keyring-test directory.

  • -DWITH_NDB_TEST={ON|OFF}

    If enabled, include a set of NDB API test programs. The default is OFF.

詳見:MySQL :: MySQL 8.0 Reference Manual :: 2.8.7 MySQL Source-Configuration Options


以下是各元件或測試型別的詳細介紹。

valgrind

簡介

valgrind 是一個工具集,主要整合了:

  • memcheck :記憶體錯誤檢測器。
  • cachegrind :快取和分支預測分析器。
  • callgrind :可生成快取分析器的呼叫圖。
  • helgrind :執行緒錯誤檢測器。
  • DRD :也是執行緒錯誤檢測器。
  • massif :堆分析器,它可以幫助程式使用更少的記憶體。
  • DHAT :一種不同型別的堆分析器。使用它可以瞭解塊壽命,塊利用率和佈局效率低下的問題。

選項

mtr 提供的 valgrind 選項如下:

Options for valgrind

  callgrind             Instruct valgrind to use callgrind.
  helgrind              Instruct valgrind to use helgrind.
  valgrind              Run the "mysqltest" and "mysqld" executables using
                        valgrind with default options.
  valgrind-all          Synonym for --valgrind.
  valgrind-clients      Run clients started by .test files with valgrind.
  valgrind-mysqld       Run the "mysqld" executable with valgrind.
  valgrind-mysqltest    Run the "mysqltest" and "mysql_client_test" executable
                        with valgrind.
  valgrind-option=ARGS  Option to give valgrind, replaces default option(s), can
                        be specified more then once.
  valgrind-options=ARGS Deprecated, use --valgrind-option.
  valgrind-path=<EXE>   Path to the valgrind executable.

從程式碼看:

  1. 支援的工具不僅限於 callgrind、helgrind,還支援 memcheck、massif
  2. 只有啟用--valgrind--valgrind-mysqltest 選項,才會用到 mysql_client_test

使用方法

編譯選項-DWITH_DEBUG=1 -DWITH_VALGRIND=1

使用建議

1、推薦指令可參考 mysql-test/collections/ 目錄下的檔案 default.daily-valgrinddefault.push-valgrinddefault.weekly-valgrind

2、據我實測,如需測試 valgrind 所有功能,需在原有指令基礎上新增--valgrind選項。比如:

# 官方 collections 中的範例指令:
perl mysql-test-run.pl --timer  --force --skip-rpl --comment=all_default_valgrind --vardir=var-all_default_valgrind --skip-ndb

# 新增 --valgrind
perl mysql-test-run.pl --timer  --force --skip-rpl --comment=all_default_valgrind --vardir=var-all_default_valgrind --skip-ndb --valgrind

3、在同時啟用 ASAN 和 valgrind 的情況下,並在執行 mtr 時新增--valgrind選項,mtr 會因 valgrind memcheck 與 asan 衝突而 crash,因此,valgrind 與 asan 不建議同時啟用

Sanitizier

Sanitizers 是谷歌發起的開源工具集,已經成為靜態和動態程式碼分析的利器,可以檢查記憶體錯誤、未初始化的讀取、執行緒安全和未定義的行為等相關的問題。

優點:與同型別分析工具相比,Sanitizers 帶來的效能損失通常要小得多,而且往往提供的資訊更詳細。

缺點:程式碼(可能還有工具鏈的一部分)需要使用附加的標誌重新編譯。

Sanitizers 包括如下元件:

  • AddressSanitizer/ASAN

    檢查記憶體地址相關問題,包括記憶體漏失、釋放後使用、重複釋放、堆溢位、棧溢位等問題。

    通過編譯插樁(CTI) ,能夠發現此堆/棧/全域性變數讀寫溢位,記憶體洩露等問題,並將資訊直接列印到紀錄檔中。

    ASAN 是一個快速的記憶體錯誤檢測工具。它非常快,只拖慢程式兩倍左右(比起 Valgrind 快多了)

    它包括一個編譯器 instrumentation 模組和一個提供malloc()/free() 替代項的執行時庫。

  • LeakSanitizer/LSAN

    檢查記憶體漏失問題。它是整合在 Address Sanitizer 中的一個相對獨立的工具,它工作在檢查過程的最後階段。

  • UndefinedBehaviorSanitizer/UBSAN

    檢測未定義行為(使用空指標、有符號整數溢位等)。

  • ThreadSanitizer/TSAN

    檢查執行緒資料競爭和死鎖問題。

  • MemorySanitizer/MSAN

    檢查使用未初始化記憶體問題。

  • 核心 Sanitizer包括KASANKMSAN

Sanitizers 專案本是 LLVM 專案的一部分,但 GNU 也將該系列工具加入到了自家的 GCC 編譯器中(clang 當然也支援)。

  • GCC 4.8 版本開始支援 Address SanitizerThread Sanitizer
  • GCC 4.9 版本開始支援 Leak SanitizerUndefinedBehaviorSanitizer

ASAN

簡介

ASAN/AddressSanitizer 能檢測很多種記憶體錯誤,主要包含如下類別:

  • Out-of-bounds accesses to heap, stack and globals
  • Use-after-free
  • Use-after-return (clang flag -fsanitize-address-use-after-return=(never|runtime|always) default: runtime)
    • Enable with: ASAN_OPTIONS=detect_stack_use_after_return=1 (already enabled on Linux).
    • Disable with: ASAN_OPTIONS=detect_stack_use_after_return=0.
  • Use-after-scope (clang flag -fsanitize-address-use-after-scope)
  • Double-free, invalid free
  • Memory leaks (experimental)

更詳細的範例 case:參考 https://learn.microsoft.com/zh-cn/cpp/sanitizers/asan-error-examples?view=msvc-170

效能影響:使用 ASAN 後,效能會降低 2 倍。

使用方法

安裝:有一個單獨的動態庫libasan6.so,會隨 gcc 安裝。

編譯選項-DWITH_DEBUG=1 -DWITH_ASAN=1,可選擇啟用-DWITH_ASAN_SCOPE=1

驗證版本:8.0.29

MTR 選項--sanitize

使用建議

1、ASAN 功能強大,相較於 valgrind,對效能影響小很多,建議作為主要的記憶體檢測工具

2、由於 mtr 需要用到/usr/bin/perl,因此,有可能出現 perl 自身某些函數的記憶體漏失問題被 Leak Sanitizer 檢測到,導致 mtr 測試失敗,此時,將問題函數新增到lsan.supp檔案即可解決。比如 Ubuntu 22.04 perl v5.34.0 會遇到記憶體漏失,同樣的,Ubuntu 20.04 perl v5.30.0 就無該問題。

mysql-test/asan.supp 範例

# 語法
interceptor_via_fun:NameOfCFunctionToSuppress
interceptor_via_fun:-[ClassName objCMethodToSuppress:]
interceptor_via_lib:NameOfTheLibraryToSuppress

# 範例
interceptor_via_fun:Perl_safesyscalloc
interceptor_via_fun:Perl_safesysmalloc

參考:

mysql-test/lsan.supp 範例

# LSAN suppressions for gcc/clang
leak:Perl_safesyscalloc
leak:Perl_safesysmalloc
leak:Perl_safesysrealloc
leak:Perl_savesharedpv
leak:Perl_Slab_Alloc
leak:Perl_newUNOP_AUX
leak:Perl_newSTATEOP
leak:Perl_pmruntime
leak:/usr/bin/perl
leak:/lib64/libperl.so.*
leak:/bin/bash
leak:/usr/bin/zip
# OpenLDAP bug 9081
# Fixed in 2.4.49, we build with 2.4.48
leak:ldap_initialize

# sasl_client_init will load all available plugins with _sasl_load_plugins().
# It seems some of the SASL plugin have leaks.
# Both LSAN and Valgrind report leaks.
leak:sasl_client_add_plugin

該內容來源於原始碼檔案,可見官方知曉 Perl 高版本的記憶體漏失問題,以此方式來忽略。

指令範例

Ubuntu 22.04 X86_64 執行:

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --vardir=var-rpl --suite=rpl --sanitize
perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --sanitize

LSAN

簡介

LSAN/LeakSanitizer 用於記憶體漏失檢測。

效能影響:使用 LSAN 後,除了執行的最後階段會有一個記憶體漏失檢測之外,幾乎沒有效能開銷。

使用方法

安裝:有一個單獨的動態庫liblsan0.so,會隨 gcc 安裝。

編譯選項-DWITH_DEBUG=1 -DWITH_LSAN=1

驗證版本:8.0.29

MTR 選項--sanitize

使用建議

  1. 由於 ASAN 整合了 LSAN,因此,只有不使用 ASAN、僅使用 LSAN 的情況下才需要設定該選項。
  2. lsan.supp 格式見 「ASAN」小節。

指令範例

只要編譯時啟用 ASAN 或 LSAN,在執行時新增--sanitize 選項即可。

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --sanitize

UBSAN

簡介

UBSAN/UndefinedBehaviorSanitizer 是針對未定義行為的檢測器,速度很快。

UBSAN 需要在編譯時修改程式,以捕獲程式執行期間的各種未定義行為。比如:

  • 陣列下標越界:Array subscript out of bounds, where the bounds can be statically determined
  • 位移位超過資料型別邊界:Bitwise shifts that are out of bounds for their data type
  • 解除對未對齊指標或空指標的關聯:Dereferencing misaligned or null pointers
  • 有符號整數溢位:Signed integer overflow
  • 浮點數型別轉換導致的溢位:Conversion to, from, or between floating-point types which would overflow the destination

更多行為詳見 :

UndefinedBehaviorSanitizer — Clang 17.0.0git documentation (llvm.org)

效能影響:UBSAN 的執行時成本很小,對地址空間佈局或 ABI 沒有影響。

使用方法

安裝:有一個單獨的動態庫libubsan1.so,會隨 gcc 安裝。

編譯選項-DWITH_DEBUG=1 -DWITH_UBSAN=1

驗證版本:8.0.29

MTR 選項--sanitize

使用建議

  1. 與 ASAN、TSAN、gcov、gprof 都相容,可一起啟用
  2. 若想要某些 case 跳過 UBSAN 的檢查,可參照include/not_ubsan.inc。目前只有如下 case 會跳過 UBSAN:
./t/innodb_tmp_table_heap_to_disk.test
./t/ssl-big.test
./t/count_distinct3.test
./t/multi_update2.test
./t/ds_mrr-big.test

./suite/gis/t/gis_not_ubsan.test
./suite/binlog_gtid/t/binlog_warning_same_server_id.test

指令範例

只要編譯時啟用 UBSAN,在執行時新增--sanitize 選項即可。

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --sanitize

TSAN

簡介

TSAN/ThreadSanitizer 是用於檢測資料競爭和執行緒死鎖的工具。

效能影響:引入 TSAN 後,會降低 5-15 倍效能,同時,記憶體佔用率會提升 5-10 倍。

使用方法

安裝:有一個單獨的動態庫libtsan0.so,會隨 gcc 安裝。

編譯選項-DWITH_DEBUG=1 -DWITH_TSAN=1

驗證版本:8.0.29、8.0.32

MTR 選項--sanitize

使用建議

1、TSAN 與 ASAN 不相容(一起使用 cmake 會報錯"No mysys timer support detected"),但TSAN 與 UBSAN、VALGRIND 相容

2、對 TSAN 的支援是實驗性的,尚不成熟,不建議使用

-- Performing Test HAVE_SANITIZE_SCOPE
-- Performing Test HAVE_SANITIZE_SCOPE - Success
CMake Warning at CMakeLists.txt:1101 (MESSAGE):
  Thread sanitizer support is currently experimental.


-- Performing Test C_LD_LLD_RESULT
-- Performing Test C_LD_LLD_RESULT - Failed
-- Performing Test CXX_LD_LLD_RESULT
-- Performing Test CXX_LD_LLD_RESULT - Failed
-- Performing Test C_LD_GOLD_RESULT
-- Performing Test C_LD_GOLD_RESULT - Failed
-- Performing Test CXX_LD_GOLD_RESULT
-- Performing Test CXX_LD_GOLD_RESULT - Failed
-- Local boost dir /data/work/mysql/boost_1_77_0
-- Found /data/work/mysql/boost_1_77_0/boost/version.hpp
-- BOOST_VERSION_NUMBER is #define BOOST_VERSION 107700
-- BOOST_INCLUDE_DIR /data/work/mysql/boost_1_77_0
-- Looking for pthread.h
-- Looking for pthread.h - not found
-- Could NOT find Threads (missing: Threads_FOUND)
......
-- Looking for timer_create # 由於 timer_create/timer_settime 函數確實存在,嘗試調整過 cmake,後續會報一系列錯誤,該問題不太好調。
-- Looking for timer_create - not found
-- Looking for timer_settime
-- Looking for timer_settime - not found
-- Looking for kqueue
-- Looking for kqueue - not found
-- Performing Test HAVE_SETNS
-- Performing Test HAVE_SETNS - Failed
-- Looking for EVFILT_TIMER
-- Looking for EVFILT_TIMER - not found
CMake Error at configure.cmake:334 (MESSAGE):
  No mysys timer support detected!
Call Stack (most recent call first):
  CMakeLists.txt:1487 (INCLUDE)

3、如果某些資料競爭或死鎖情況是符合預期的,可以通過 mysql-test/tsan.supp 跳過。

mysql-test/tsan.supp 範例

#
# Blacklist for Thread Sanitizer.
# Thread Sanitizer can be enabled with -DWITH_TSAN=1
#
# Suppression syntax is documented here:
# https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
#

race:innobase

race:client/dump/
deadlock:client/dump/

race:perfschema

race:plugin_vars_free_values
race:log_builtins_filter_run
race:MY_LOCALE_ERRMSGS::destroy
race:get_one_variable_ext
race:mysql_set_character_set_with_default_collation

race:ngs::Scheduler_dynamic::wait_if_idle_then_delete_worker
race:ngs::Socket_events::break_loop

deadlock:find_sys_var_ex
deadlock:Persisted_variables_cache::lock

signal:my_print_stacktrace

指令範例

只要編譯時啟用 TSAN,在執行時新增--sanitize 選項即可。

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --sanitize

存在問題

測試時,在 install database 階段,執行緒之間就會有大量 data race,報錯範例如下:

// 報錯資訊位於 mysql-test/var-main-tsan/log/bootstrap.log
==================
WARNING: ThreadSanitizer: data race (pid=65314)
  Read of size 4 at 0x555ae20c13b0 by thread T4:
    #0 fil_validate_skip /data/work/mysql/mysql-server/storage/innobase/fil/fil0fil.cc:1953 (mysqld+0x541018e)
    #1 fil_aio_wait(unsigned long) /data/work/mysql/mysql-server/storage/innobase/fil/fil0fil.cc:8234 (mysqld+0x54109ca)
    #2 io_handler_thread /data/work/mysql/mysql-server/storage/innobase/srv/srv0start.cc:279 (mysqld+0x5143b04)
    ......
    #12 std::thread::_State_impl<std::thread::_Invoker<std::tuple<Detached_thread, void (*)(unsigned long), unsigned long> > >::_M_run() /usr/include/c++/11/bits/std_thread.h:211 (mysqld+0x5159b59)
    #13 <null> <null> (libstdc++.so.6+0xdc2b2)

  Previous write of size 4 at 0x555ae20c13b0 by thread T3:
    #0 fil_validate_skip /data/work/mysql/mysql-server/storage/innobase/fil/fil0fil.cc:1953 (mysqld+0x54101a7)
    #1 fil_aio_wait(unsigned long) /data/work/mysql/mysql-server/storage/innobase/fil/fil0fil.cc:8234 (mysqld+0x54109ca)
    #2 io_handler_thread /data/work/mysql/mysql-server/storage/innobase/srv/srv0start.cc:279 (mysqld+0x5143b04)
    ......
    #12 std::thread::_State_impl<std::thread::_Invoker<std::tuple<Detached_thread, void (*)(unsigned long), unsigned long> > >::_M_run() /usr/include/c++/11/bits/std_thread.h:211 (mysqld+0x5159b59)
    #13 <null> <null> (libstdc++.so.6+0xdc2b2)

  Location is global 'fil_validate_skip()::fil_validate_count' of size 4 at 0x555ae20c13b0 (mysqld+0x000007a5c3b0)

  Thread T4 (tid=65320, running) created by thread T1 at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xdc388)
    ......
    #8 handle_bootstrap /data/work/mysql/mysql-server/sql/bootstrap.cc:327 (mysqld+0x387778f)
    #9 pfs_spawn_thread /data/work/mysql/mysql-server/storage/perfschema/pfs.cc:2942 (mysqld+0x56751fb)

  Thread T3 (tid=65319, running) created by thread T1 at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xdc388)
    ......
    #8 handle_bootstrap /data/work/mysql/mysql-server/sql/bootstrap.cc:327 (mysqld+0x387778f)
    #9 pfs_spawn_thread /data/work/mysql/mysql-server/storage/perfschema/pfs.cc:2942 (mysqld+0x56751fb)

SUMMARY: ThreadSanitizer: data race /data/work/mysql/mysql-server/storage/innobase/fil/fil0fil.cc:1953 in fil_validate_skip
==================

......

install database 階段,類似的報錯有 200 多個。雖然可以通過tsan.supp 檔案跳過,但畢竟報錯涉及較多函數,若全部跳過,可能會影響對正常情況下資料競爭的判斷。因此,個人暫不建議使用

MSAN

簡介

MSAN/MemorySanitizer 用於檢測對未初始化記憶體的讀取(uninitialized reads)問題。

效能影響:引入 MSAN 後,效能會降低 3 倍。

使用方法

編譯選項-DWITH_DEBUG=1 -DWITH_MSAN=1

驗證版本:8.0.29

MTR 選項--sanitize

使用建議

  1. 對 MSAN 的支援是實驗性的,尚不成熟,且與 ASAN 不相容,考慮到 ASAN 的強大,因此,建議使用 ASAN,不建議使用 MSAN
CMake Warning at CMakeLists.txt:1080 (MESSAGE):
  Memory sanitizer support is currently experimental.


CMake Error at CMakeLists.txt:1107 (MESSAGE):
  Cannot use AddressSanitizer and MemorySanitizer together

指令範例

只要編譯時啟用 MSAN,在執行時新增--sanitize 選項即可。

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --sanitize

程式碼覆蓋率測試

簡介

gcov 用於分析程式碼覆蓋率,gprof 用於分析 gcov 生成的統計資料,二者一般一起使用。

gprof 只支援 linux 作業系統,不支援 MacOS

官方手冊:

Gcov (Using the GNU Compiler Collection (GCC))

gprof(1) - Linux manual page (man7.org)

超級方便的 Linux 自帶效能分析工具!gprof 介紹、安裝、使用及實踐 - 知乎 (zhihu.com)

使用方法

編譯選項:-DWITH_DEBUG=1 -DENABLE_GCOV=1 -DENABLE_GPROF=1

使用建議

  1. 與 ASAN、UBSAN 相容,建議與 ASAN、UBSAN 同時啟用(未驗證與 valgrind、MSAN、TSAN 的相容性)。
  2. 不能在make install的安裝目錄測試,必須在 執行編譯的原始碼目錄測試。
    1. 原始碼根目錄/build-debug/mysql-test/mysql-test-run.pl (本人的編譯目錄是 build-debug)只是封裝了一層對 原始碼根目錄/mysql-test/mysql-test-run.pl 的呼叫:
[apps@node6 mysql-test]$ pwd
/home/apps/mtr/mysql-oracle/mysql-8.0.26/build-debug/mysql-test

[apps@node6 mysql-test]$ cat mysql-test-run.pl
#!/usr/bin/perl
# Call mtr in out-of-source build
$ENV{MTR_BINDIR} = '/home/apps/mtr/mysql-oracle/mysql-8.0.26/build-debug';
chdir('/home/apps/mtr/mysql-oracle/mysql-8.0.26/mysql-test');
exit(system($^X, '/home/apps/mtr/mysql-oracle/mysql-8.0.26/mysql-test/mysql-test-run.pl', @ARGV) >> 8);

注意:

  1. gcov、gprof 執行需要較大記憶體,尤其是開啟-big-test的情況下。
  2. 官方 collections/ 中沒有 gcov 的推薦用法。
  3. mysql-test/README.gcov 檔案的最後修改日期是 2006 年,已過時,沒有參考價值。

範例

直接在安裝目錄(make install)執行測試,報錯:

wslu@ubuntu:/data/work/mysql/mysql80-install.bak_asan_ubsan_gcov_gprof/mysql-test$ ./mtr --gcov
Logging: ./mtr  --gcov

MySQL Version 8.0.29
mysql-test-run: *** ERROR: Coverage test needs the source - please use source dist

執行編譯的目錄(比如 console-build-debug/mysql-test) 執行:

wslu@ubuntu:/data/work/mysql/mysql-server/console-build-debug/mysql-test$ ./mtr --gcov -big-test
Logging: /data/work/mysql/mysql-server/mysql-test/mysql-test-run.pl  --gcov -big-test
MySQL Version 8.0.29
Checking supported features
 - Binaries are debug compiled
Purging gcov information from '/data/work/mysql/mysql-server'...
Using suite(s): auth_sec,binlog,binlog_gtid,binlog_nogtid,clone,collations,component_keyring_file,connection_control,encryption,federated,funcs_2,gcol,gis,information_schema,innodb,innodb_fts,innodb_gis,innodb_undo,innodb_zip,interactive_utilities,json,main,opt_trace,parts,perfschema,query_rewrite_plugins,rpl,rpl_gtid,rpl_nogtid,secondary_engine,service_status_var_registration,service_sys_var_registration,service_udf_registration,sys_vars,sysschema,test_service_sql_api,test_services,x
Collecting tests
 - Adding combinations for binlog
 - Adding combinations for binlog_gtid
 - Adding combinations for binlog_nogtid
 - Adding combinations for rpl
 - Adding combinations for rpl_gtid
 - Adding combinations for rpl_nogtid
Checking leftover processes
Removing old var directory
Creating var directory '/data/work/mysql/mysql-server/console-build-debug/mysql-test/var'
Installing system database
Using parallel: 1

==============================================================================
                  TEST NAME                       RESULT  TIME (ms) COMMENT
------------------------------------------------------------------------------
[  0%] binlog_gtid.binlog_xa_select_gtid_executed_explicitly_crash  [ disabled ]   Bug#28588717 Fails both on FreeBSD and other platforms
[  0%] binlog_nogtid.binlog_gtid_next_xa         [ disabled ]   BUG#33650776 Failure of XA COMMIT of prepared txn, can result in txn rollback
[  0%] sys_vars.innodb_log_writer_threads_basic  [ disabled ]   Bug#32129814 SYS_VARS.INNODB_LOG_WRITER_THREADS_BASIC TIMES OUT SPORADICALLY ON PB2
[  0%] sysschema.v_wait_classes_global_by_avg_latency  [ disabled ]   BUG#21550054 Test fails too often.
[  0%] binlog_gtid.binlog_gtid_mix_ok_packet_all_gtids 'mix'  [ pass ]    770
[  0%] binlog_gtid.binlog_gtid_mix_response_packet 'mix'  [ pass ]   6474
[  0%] binlog_gtid.binlog_xa_trx_gtid_response_packet 'mix'  [ pass ]    683
......
[  0%] binlog_gtid.binlog_gtid_errors 'mix'      [ pass ]   1583
......

如果想執行測試後分析 gmon.out ,則可新增 -gprof 引數(僅支援 linux):

wslu@ubuntu:/data/work/mysql/mysql-server/console-build-debug/mysql-test$ ./mtr --gcov -gprof -big-test

那麼,在 gcov 執行完成後,mtr 就會自動呼叫 gprof 解析gmon.out檔案

存在問題

但在 CentOS 7.6(雲伺服器 4C8G SSD)實測時遇到問題——gprof 解析 gmon.out 時特別耗時,雖然該程序 CPU 佔用率 100%,看起來還在執行,但並無任何輸出。

比如,在編譯的原始碼目錄中執行:

➜  mysql-test git:(heads/mysql-8.0.26) ✗ ps -axf | grep mysql
30604 ?        SN     0:00  \_ perl mysql-test-run.pl --parallel=4 --timer --debug-server --force --testcase-timeout=180 --suite-timeout=1800 --comment=all-default-debug --vardir=var-all-default --skip-combinations --unit-tests-report --no-skip --exclude-platform=windows --skip-ndb --max-test-fail=0 --suite=rpl -gcov -gprof
30611 ?        SN     0:30      \_ /usr/bin/perl /home/wslu/work/mysql/mysql-server/mysql-test/mysql-test-run.pl --parallel=4 --timer --debug-server --force --testcase-timeout=180 --suite-timeout=1800 --comment=all-default-debug --vardir=var-all-default --skip-combinations --unit-tests-report --no-skip --exclude-platform=windows --skip-ndb --max-test-fail=0 --suite=rpl -gcov -gprof
32759 ?        SN     0:44          \_ /usr/bin/perl /home/wslu/work/mysql/mysql-server/mysql-test/mysql-test-run.pl --parallel=4 --timer --debug-server --force --testcase-timeout=180 --suite-timeout=1800 --comment=all-default-debug --vardir=var-all-default --skip-combinations --unit-tests-report --no-skip --exclude-platform=windows --skip-ndb --max-test-fail=0 --suite=rpl -gcov -gprof
 2829 ?        SN     0:00          |   \_ sh -c gprof /home/wslu/work/mysql/mysql-server/build-debug/runtime_output_directory/mysqld /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gmon.out 2 > /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.err > /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.msg
 2830 ?        RN     7:07          |       \_ gprof /home/wslu/work/mysql/mysql-server/build-debug/runtime_output_directory/mysqld /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gmon.out 2

gmon.out 檔案只有 61MB,但 gprof 在解析 gmon.out 時,長達 23 小時無任何輸出。

➜  mysql-test git:(heads/mysql-8.0.26) ✗ ls /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gmon.out -lh
-rw-r--r-- 1 wslu wslu 61M Mar 27 20:21 /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gmon.out
➜  mysql-test git:(heads/mysql-8.0.26) ✗ ll /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.err
-rw-r--r-- 1 wslu wslu 0 Mar 28 09:23 /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.err
➜  mysql-test git:(heads/mysql-8.0.26) ✗ ll  /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.msg
-rw-r--r-- 1 wslu wslu 0 Mar 28 09:23 /home/wslu/work/mysql/mysql-server/mysql-test/var-all-default/3/mysqld.6/data/gprof.msg

單元測試

簡介

MySQL 使用 TAP(Test Anything Protocol) 和 Google Test Framework 來實現單元測試。

MyTAP

TAP 是 Perl 與測試模組之間所使用的簡單的基於文字的介面,主要用於開發 Perl 和 PHP 模組。範例如下:

TAP version 13
ok 1 - testNewArrayIsEmpty(ArrayTest)
ok 2 - testArrayContainsAnElement(ArrayTest)
not ok 3 - Failure: testFailure(FailureErrorTest)
  ---
  message: 'Failed asserting that <integer:2> matches expected value <integer:1>.'
  severity: fail
  data:
    got: 2
    expected: 1
  ...
not ok 4 - Error: testError(FailureErrorTest)
1..4

為了實現 C/C++ 的單元測試,MySQL 開發了一個用於生成 TAP 文字的庫libmytap.a,原始碼路徑位於unittest/mytap/

Google Test Framework

Google Test Framework,與 MyTAP 類似,也是一個單元測試框架,但提供了更豐富的功能:

  • A rich set of predicates
  • User-defined predicates and assertions
  • Automatic test registration
  • Nice error reporting when a predicate fails (with line number, expected and actual values, and additional comments)
  • Test fixtures, and setup/teardown logic
  • Death tests
  • Disabled tests
  • Test filtering and shuffling

參考:

使用方法

編譯選項-DWITH_DEBUG=1 -DWITH_UNIT_TESTS={ON|OFF},預設是 ON

執行路徑:必須在編譯的原始碼目錄中執行。

使用方法

  1. 編譯後,在執行編譯(cmake)的目錄執行 make testmake test-unit 指令,雖然按手冊描述兩個指令都能實現單元測試效果,但實測make test-unit會輸出更詳細的資訊,因此,建議使用make test-unit
  2. 編譯後,在編譯目錄/mysql-test 中執行 mtr 指令時,新增--unit-tests-report 選項。

注意事項

若啟用了 ASAN:

  1. 直接在編譯目錄執行make test-unit,可能會因 ASAN 檢測到單元測試程式碼有記憶體錯誤(RUN_ALL_TESTS()的子函數)而導致 case 失敗。
  2. 通過 mtr 指令來執行單元測試時,也可能會遇到 ASAN 檢測到記憶體錯誤或記憶體漏失,即使按如下方式修改 .supp 檔案,也無法跳過:
    1. 若是 AddressSanitizer 範疇中的錯誤,比如下表中的 heap-buffer-overflow,在asan.supp 檔案新增 interceptor_via_fun:RUN_ALL_TESTS ,無法跳過該錯誤。
    2. 同理,如果是 ASAN 中的LeakSanitizer檢測到記憶體漏失,在lsan.supp 檔案新增 leak:RUN_ALL_TESTS,無法跳過該錯誤。
==228225==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x620000098e90 at pc 0x5570c34c26fb bp 0x7ffe1d0d0590 sp 0x7ffe1d0d0580
READ of size 2 at 0x620000098e90 thread T0
    #0 0x5570c34c26fa in modify_all_zh_pages /data/work/mysql/mysql-server/strings/ctype-uca.cc:4178
    #1 0x5570c34c4d89 in init_weight_level /data/work/mysql/mysql-server/strings/ctype-uca.cc:4287
    ......
    #16 0x5570c36724b8 in testing::UnitTest::Run() /data/work/mysql/mysql-server/extra/googletest/googletest-release-1.11.0/googletest[表情]c/gtest.cc:5438
    #17 0x5570c3285445 in RUN_ALL_TESTS() /data/work/mysql/mysql-server/extra/googletest/googletest-release-1.11.0/googletest/include/gtest/gtest.h:2490
    #18 0x5570c3284f94 in main /data/work/mysql/mysql-server/unittest/gunit/gunit_test_main.cc:150
    #19 0x7f680a423d8f in __libc_start_call_main ../sysdeps/nptl[表情]bc_start_call_main.h:58
    #20 0x7f680a423e3f in __libc_start_main_impl ..[表情]u[表情]bc-start.c:392
    #21 0x5570c2682f34 in _start (/data/work/mysql/mysql-server/console-build-debug/runtime_output_directory/merge_small_tests-t+0x26a9f34)

綜上,執行單元測試時,不建議同時啟用 ASAN

指令範例

make test 範例

➜  console-build-debug git:(my_learn_8.0.29) ✗ make test
Running tests...
Test project /Users/wslu/work/mysql/mysql-server-8.0/console-build-debug
        Start   1: hp_test1
  1/223 Test   #1: hp_test1 ...................................................   Passed    0.49 sec
        Start   2: hp_test2
  2/223 Test   #2: hp_test2 ...................................................   Passed    0.74 sec
        Start   3: pfs_instr_class
  3/223 Test   #3: pfs_instr_class ............................................   Passed    0.54 sec
 ......
        Start 206: routertest_component_rest_metadata_cache
 206/223 Test #206: routertest_component_rest_metadata_cache ...................***Failed   13.37 sec
        Start 207: routertest_component_rest_mock_server
 207/223 Test #207: routertest_component_rest_mock_server ......................   Passed   23.60 sec
        Start 208: routertest_component_rest_router
 208/223 Test #208: routertest_component_rest_router ...........................   Passed    3.87 sec
        Start 209: routertest_component_rest_routing
 209/223 Test #209: routertest_component_rest_routing ..........................***Failed  145.64 sec
        Start 210: routertest_component_rest_connection_pool
 210/223 Test #210: routertest_component_rest_connection_pool ..................   Passed    5.71 sec
        Start 211: routertest_component_router_configuration_errors
 211/223 Test #211: routertest_component_router_configuration_errors ...........   Passed    5.15 sec
        Start 212: routertest_component_routing
 212/223 Test #212: routertest_component_routing ...............................***Failed   39.04 sec
        Start 213: routertest_component_routing_connection
 213/223 Test #213: routertest_component_routing_connection ....................***Failed  117.32 sec
        Start 214: routertest_component_routing_strategy
 214/223 Test #214: routertest_component_routing_strategy ......................   Passed   83.88 sec
        Start 215: routertest_component_sd_notify
 215/223 Test #215: routertest_component_sd_notify .............................   Passed   22.63 sec
        Start 216: routertest_component_shutdown
 216/223 Test #216: routertest_component_shutdown ..............................   Passed    4.86 sec
        Start 217: routertest_component_state_file
 217/223 Test #217: routertest_component_state_file ............................   Passed   22.92 sec
        Start 218: routertest_component_user_option
 218/223 Test #218: routertest_component_user_option ...........................   Passed    0.74 sec
        Start 219: routertest_component_metadata_http_auth_backend
 219/223 Test #219: routertest_component_metadata_http_auth_backend ............***Failed   76.95 sec
        Start 220: routertest_component_socket_close
 220/223 Test #220: routertest_component_socket_close ..........................   Passed  162.52 sec
        Start 221: routertest_component_routing_splicer
 221/223 Test #221: routertest_component_routing_splicer .......................   Passed  287.30 sec
        Start 222: routertest_component_mock_server
 222/223 Test #222: routertest_component_mock_server ...........................   Passed   20.44 sec
        Start 223: routertest_integration_routing_reuse
 223/223 Test #223: routertest_integration_routing_reuse .......................   Passed  440.36 sec

97% tests passed, 6 tests failed out of 223

Total Test time (real) = 2940.80 sec

The following tests FAILED:
   14 - merge_small_tests (Failed)
  206 - routertest_component_rest_metadata_cache (Failed)
  209 - routertest_component_rest_routing (Failed)
  212 - routertest_component_routing (Failed)
  213 - routertest_component_routing_connection (Failed)
  219 - routertest_component_metadata_http_auth_backend (Failed)
Errors while running CTest
Output from these tests are in: /Users/wslu/work/mysql/mysql-server-8.0/console-build-debug/Testing/Temporary/LastTest.log
Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely.
make: *** [test] Error 8

make test-unit 範例

wslu@ubuntu:/data/work/mysql/mysql-server/console-build-debug$ head -n 100 /tmp/maketestunit.out
Test project /data/work/mysql/mysql-server/console-build-debug
        Start  14: merge_small_tests
        Start  15: merge_large_tests
  1/223 Test  #14: merge_small_tests ..........................................***Failed   70.09 sec
[==========] Running 2386 tests from 132 test suites.
[----------] Global test environment set-up.
[----------] 5 tests from BoundsCheckedArrayDeathTest
[ RUN      ] BoundsCheckedArrayDeathTest.BoundsCheckRead
[       OK ] BoundsCheckedArrayDeathTest.BoundsCheckRead (616 ms)
[ RUN      ] BoundsCheckedArrayDeathTest.BoundsCheckAssign
[       OK ] BoundsCheckedArrayDeathTest.BoundsCheckAssign (329 ms)
[ RUN      ] BoundsCheckedArrayDeathTest.BoundsCheckPopFront
[       OK ] BoundsCheckedArrayDeathTest.BoundsCheckPopFront (276 ms)
[ RUN      ] BoundsCheckedArrayDeathTest.BoundsCheckResize
[       OK ] BoundsCheckedArrayDeathTest.BoundsCheckResize (277 ms)
[ RUN      ] BoundsCheckedArrayDeathTest.BoundsCheckResizeAssign
[       OK ] BoundsCheckedArrayDeathTest.BoundsCheckResizeAssign (290 ms)
[----------] 5 tests from BoundsCheckedArrayDeathTest (1794 ms total)

[----------] 1 test from DebugDeathTest
[ RUN      ] DebugDeathTest.Suicide
[       OK ] DebugDeathTest.Suicide (178 ms)
[----------] 1 test from DebugDeathTest (178 ms total)

......

216/223 Test   #1: hp_test1 ...................................................   Passed    0.96 sec
        Start  12: pfs_misc
217/223 Test  #10: pfs_noop ...................................................   Passed    0.76 sec
        Start  83: basic
218/223 Test  #83: basic ......................................................   Passed    0.12 sec
        Start  79: skip
219/223 Test  #79: skip .......................................................   Passed    0.07 sec
        Start  80: todo
220/223 Test  #12: pfs_misc ...................................................   Passed    0.91 sec
        Start  81: skip_all
221/223 Test  #80: todo .......................................................   Passed    0.06 sec
        Start  82: no_plan
222/223 Test  #81: skip_all ...................................................   Passed    0.07 sec
223/223 Test  #82: no_plan ....................................................   Passed    0.03 sec

94% tests passed, 14 tests failed out of 223

Total Test time (real) = 4334.59 sec

The following tests FAILED:
   14 - merge_small_tests (Failed)
   15 - merge_large_tests (Failed)
   56 - gcs_xcom_xcom_cache (Subprocess killed)
   57 - gcs_xcom_control_interface (Failed)
   65 - merge_temptable_tests-t (Failed)
  187 - routertest_component_bootstrap (Subprocess aborted)
  189 - routertest_component_bootstrap_clusterset (Subprocess aborted)
  191 - routertest_component_bootstrap_tls_endpoint (Subprocess aborted)
  192 - routertest_component_clusterset (Subprocess aborted)
  197 - routertest_component_gr_notifications (Failed)
  205 - routertest_component_rest_api_enable (Subprocess aborted)
  213 - routertest_component_routing_connection (Subprocess aborted)
  221 - routertest_component_routing_splicer (Timeout)
  223 - routertest_integration_routing_reuse (Failed)
Errors while running CTest

通過 mtr 執行單元測試

編譯目錄/mysql-test 執行如下指令:

perl mysql-test-run.pl --timer --max-test-fail=0 --force --parallel=1 --max-test-fail=0 --vardir=var-binlog --suite=binlog --unit-tests-report

mtr 會首先執行 binlog suite 的所有 case,之後才會執行單元測試。

壓力測試

簡介

涉及壓力測試的有兩部分:

壓力測試 suites

只有兩個:

  • stress
  • innodb_stress

如需要新增新 case,參考對應 suite 已有 case 照貓畫虎即可,後續文章會詳解介紹語法。

mysql-stress-test.pl

mysql-test-run.pl 呼叫,引數是--stress

使用說明位於mysql-test/README.stress檔案:

The stress script is designed to perform testing of the MySQL server in
a multi-threaded environment.

All functionality regarding stress testing is implemented in the
mysql-stress-test.pl script.

The stress script allows:

 - To stress test the mysqltest binary test engine.
 - To stress test the regular test suite and any additional test suites
   (such as mysql-test-extra-5.0).
 - To specify files with lists of tests both for initialization of
   stress db and for further testing itself.
 - To define the number of threads to be concurrently used in testing.
 - To define limitations for the test run. such as the number of tests or
   loops for execution or duration of testing, delay between test
   executions, and so forth.
 - To get a readable log file that can be used for identification of
   errors that occur during testing.

There are two ways to run the mysql-stress-test.pl script:

 - For most cases, it is enough to use the options below for starting
   the stress test from the mysql-test-run wrapper. In this case, the
   server is run automatically, all preparation steps are performed,
   and after that the stress test is started.

 - In advanced case, you can run the mysql-stress-test.pl script directly.
   But this requires that you perform some preparation steps and to specify
   a bunch of options as well, so this invocation method may be a bit
   complicated.

可見,有兩種用法:

  • 大部分情況下,通過 mysql-test-run.pl --stress=[option1,option2,...] 執行即可,該指令碼實現了準備階段、壓力測試階段所需的工作。

  • 更高階的用法是直接執行mysql-stress-test.pl 指令碼,這就需要自行實現準備階段、測試階段所需的工作。主要包括:

    • --stress-init-file[=path]

      file_name is the location of the file that contains the list of tests to be run once to initialize the database for the testing. If missing, the default file is stress_init.txt in the test suite directory.

    • --stress-tests-file[=file_name]

      Use this option to run the stress tests. file_name is the location of the file that contains the list of tests. If file_name is omitted, the default file is stress-test.txt in the stress suite directory. (See --stress-suite-basedir).

其他引數見手冊 MySQL: mysql-stress-test.pl — Server Stress Test Program

指令範例

單獨執行壓力測試 suites

沒找到手冊說明,據我理解,只要未主動關閉單元測試標記(-DWITH_UNIT_TESTS={ON|OFF}選項,預設是開啟的),就肯定會編譯生成 stress suite。

在 Ubuntu 22.04 X86_64 執行測試,成功。

wslu@ubuntu:/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test$ perl mysql-test-run.pl --force --timer    --comment=stress --vardir=var-stress  --suite=stress --no-skip --max-test-fail=30
Logging: mysql-test-run.pl  --force --timer --comment=stress --vardir=var-stress --suite=stress --no-skip --max-test-fail=30


MySQL Version 8.0.29

##############################################################################
# stress
##############################################################################
Checking supported features
 - Binaries are debug compiled
Using suite(s): stress
Collecting tests
Removing old var directory
Creating var directory '/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-stress'
Installing system database
Using parallel: 1

==============================================================================
                  TEST NAME                       RESULT  TIME (ms) COMMENT
------------------------------------------------------------------------------


[ 16%] stress.ddl_myisam                         [ pass ]  88171
[ 33%] stress.ddl_archive                        [ pass ]  11868
[ 50%] stress.ddl_csv                            [ pass ]   8007
[ 66%] stress.ddl_innodb                         [ pass ]  163638
[ 83%] stress.ddl_memory                         [ pass ]  84721
[100%] shutdown_report                           [ pass ]
------------------------------------------------------------------------------
The servers were restarted 1 times
The servers were reinitialized 0 times
Spent 356.405 of 423 seconds executing testcases

Completed: All 6 tests were successful.

mysql-stress-test.pl 使用範例

指令範例:

perl mysql-stress-test.pl
--stress-suite-basedir=/opt/qa/mysql-test-extra-5.0/mysql-test
--stress-basedir=/opt/qa/test
--server-logs-dir=/opt/qa/logs
--test-count=20
--stress-tests-file=innodb-tests.txt
--stress-init-file=innodb-init.txt
--threads=5
--suite=funcs_1
--mysqltest=/opt/mysql/mysql-5.0/client/mysqltest
--server-user=root
--server-database=test
--cleanup

官方推薦的壓力測試用法

#### 提交程式碼時執行
perl mysql-test-run.pl --force --timer    --comment=stress --vardir=var-stress  --suite=stress --no-skip --max-test-fail=30

#### 每天執行
perl mysql-test-run.pl --force --timer --big-test   --comment=stress --vardir=var-stress  --suite=stress --no-skip

##### 每週執行 basic
# 相較於提交程式碼時執行的測試指令,多了 --debug-server 選項
perl mysql-test-run.pl --debug-server --force --timer --comment=stress --vardir=var-stress  --suite=stress --no-skip
# 相較於上一條多了 --big-test
perl mysql-test-run.pl --debug-server --force --timer --big-test    --comment=stress --vardir=var-stress  --suite=stress --no-skip

#### 每天執行 valgrind
perl mysql-test-run.pl --force --timer    --comment=stress --vardir=var-stress  --suite=stress

#### 每週執行 valgrind
# 指定了 --big-test
perl mysql-test-run.pl --force --timer --big-test --testcase-timeout=60 --debug-server  --comment=stress-debug-big --vardir=var-stress-debug-big  --suite=stress

# 其他
perl mysql-test-run.pl --force --timer    --comment=stress --vardir=var-stress  --suite=stress --no-skip --max-test-fail=30
perl mysql-test-run.pl --force --timer    --comment=innodb-stress --vardir=var-innodb-stress  --suite=innodb_stress --no-skip --max-test-fail=30

注意:mysql-test/README.stress 檔案的最後修改日期是 2006 年,已過時,沒有參考價值。

結論

mtr 執行路徑

  • 程式碼覆蓋率、單元測試只能在編譯的原始碼目錄/mysql-test執行
  • 其他測試在編譯的原始碼目錄/mysql-test安裝目錄/mysql-test都可以執行
  • 如無特殊需求,更建議在安裝目錄執行 mtr 測試(目錄結構更清晰)。

測試結果及相容性

名稱 對其支援是否是實驗性的 編譯相容性(同時啟用可編譯成功,則為相容) mtr 測試結果 結論
ASAN 與 UBSAN 、Valgrind 相容。 若與 Valgrind 同時啟用(mtr 指定 --valgrind等選項),執行 mtr 測試時,會導致很多 case 因 valgrind memcheck 報錯而失敗。比如 binlog_gtid.binlog_gtid_mix_ok_packet_all_gtids ASAN 與 Valgrind 不可同時啟用,但可與 UBSAN 同時啟用。
LSAN 已整合到 ASAN,未測試。 已整合到 ASAN,無需單獨啟用。
UBSAN 與 ASAN、valgrind、TSAN 都相容。 建議與 ASAN 同時啟用
MSAN 與 ASAN 不相容,若同時啟用,編譯會報錯。 單獨啟用 MSAN,cmake 失敗,報錯。 MySQL 對其的支援是實驗性的,暫不建議使用。
TSAN 與 ASAN 不相容,若同時啟用,編譯會報錯。 與 UBSAN、Valgrind 相容。 即使只啟用 TSAN,在執行 mtr 時,install database 階段依然有大片的 data race。 MySQL 對其的支援是實驗性的,暫不建議使用。
valgrind 與 ASAN 、UBSAN 、TSAN 相容。 單獨啟用 valgrind,mtr 可正常執行完全體 valgrind 測試(mtr 指定--valgrind選項),memcheck 未報錯。 與 ASAN 同時啟用時,若執行完全體 valgrind 測試,mtr 會因 valgrind memcheck 與 asan 衝突而 crash 若要使用,不建議與 ASAN 同時啟用。
gcov/gprof 與 ASAN、UBSAN 相容。 gprof 只支援 linux,不支援 MacOS/Windows。 需要在 執行 cmake 的原始碼目錄 測試。 建議與 ASAN、UBSAN 一起啟用。
單元測試 只要是 DEBUG 版本,就會預設啟用。 與 ASAN、UBSAN 相容,其他元件未驗證,理論上也應該相容。 需要在 執行 cmake 的原始碼目錄 測試,執行make test-unit指令或./mtr --unit-tests-report。 ASAN 會檢測到部分測試 case 自身存在記憶體漏失,導致當前 case 失敗。 做單元測試時建議啟用 ASAN/UBSAN/LSAN。
壓力測試 只要編譯,就會產生 stressinnodb_stress 兩個 suite 。 mysql-stress-test.pl 需要自定義 初始化和執行的 SQL 語句,不建議使用。 測試成功。 正常執行 mtr 全量 suite 或單獨執行 stressinnodb_stress suites。

注意事項:

  1. 由於 mtr case 特別多,執行全部 case 時間過長,因此,該表中所說「正常執行」是執行一部分 suites(而不是全部)未報錯。
  2. 從執行時長來看,如需在 hyper 執行官方推薦 collections ,至少要開啟 32 並行
  3. 對於單元測試、程式碼覆蓋率測試、記憶體錯誤檢測,建議在 X86_64 平臺執行,某些選項對 ARM 平臺不相容(編譯失敗)
  4. 部分 perl 版本有記憶體漏失,會被 ASAN 檢測出來,導致 mtr 測試終止。目前驗證 Ubuntu 22.04 所用的 perl 5.34.0 存在記憶體漏失,而 Ubuntu 20.04 所用的 perl 5.30.0 不存在記憶體漏失。
  5. 由於我主要是在虛擬機器器進行驗證的,而 mtr 執行太過耗時,因此,本文章節涉及的 mtr 指令,大部分並未完整執行(進度小於 10%)。

推薦用法

本部分是個人根據官方 collections ,結合實際情況給出的建議,僅供參考。

在程式碼開發階段,統一使用 debug 版本(編譯選項-DWITH_DEBUG=1)提前發現問題:

  • push 程式碼到 dev 分支時,可參考 default.push 中的指令集。
  • merge 程式碼到 main 分支時,可參考 mysql-trunk-stage.push (與 mysql-8.0-stage.push 完全相同,是default.push的超集)中的指令集。

核心在釋出 alpha 版本前,也要用 debug 版本來驗證穩定性:

  • 單元測試
    • 編譯選項:-DWITH_DEBUG=1 -DWITH_UNIT_TESTS={ON|OFF},預設是 ON 。
    • 執行路徑:只能在編譯後的原始碼目錄執行
    • 使用方法:
      • 執行 make testmake test-unit 指令,雖然按手冊描述兩個指令都能實現單元測試效果,但實測make test-unit會輸出更詳細的資訊,因此,建議使用make test-unit
      • 執行 mtr 指令時新增 --unit-tests-report 選項也有同樣效果。
    • 注意事項:在執行單元測試時,不建議啟用 ASAN。
  • 記憶體錯誤檢測
    • 工具選擇:由於 valgrind 執行很慢,建議使用 ASAN + UBSAN 來測試。
    • 編譯選項:-DWITH_DEBUG=1 -DWITH_ASAN=1 -DUBSAN=1,可選擇啟用-DWITH_ASAN_SCOPE=1
    • 建議指令:官方並未提供推薦指令集,建議在 default.daily 的指令基礎上,新增 --sanitize 選項。
    • 指令範例:perl mysql-test-run.pl --timer --max-test-fail=0 --force --comment=var-rpl --vardir=var-rpl --suite=rpl --sanitize
  • 程式碼覆蓋率測試
    • 編譯選項:-DWITH_DEBUG=1 -DENABLE_GCOV=1 -DENABLE_GPROF=1
    • 特殊要求:必須在編譯的原始碼目錄執行測試。
    • 指令範例:./mtr --gcov --gprof -big-test --force --max-test-fail=0 --comment=gcov-gprof --vardir=var-gcov-gprof --no-skip
      • 在 gcov 執行成功後,會將程式碼覆蓋率相關資訊寫到gmon.out,之後,mtr 會自動呼叫 gprof 解析該檔案 。
  • 壓力測試:雖然 mtr 整合了mysql-stress-test.pl 指令碼,但使用該指令碼需要自行編寫 stress-init、stress-test 檔案,因此,建議直接測試 stress、innodb_stress 這兩個 suites
    • perl mysql-test-run.pl --force --timer --big-test --comment=stress --vardir=var-stress --suite=stress,innodb_stress --no-skip
  • 執行緒競爭:參考mysql-test/collections/mysql-trunk-tsan.push
    • 編譯選項:-DWITH_DEBUG=1 -DWITH_TSAN=1
    • 指令範例:perl mysql-test-run.pl --timer --debug-server --force --comment=main-tsan --vardir=var-main-tsan --suite=main
      • 在 install database 階段會檢測出大面積執行緒競爭,因此,當前版本無法使用
    • 注意事項:TSAN/ThreadSanitizer 執行速度很慢,因此,只建議執行 main suite 。

如果需要驗證 release 版本穩定性(適用於 QA、研發),可參考 default.daily 中的指令集。

  • 該指令集覆蓋了單元測試、壓力測試等。

編譯組合建議

推薦:

  • 普通 debug 版,執行 SQL 相容性測試 + 單元測試 + 壓力測試(stress/innodb_stress suite)
  • 記憶體錯誤 + 程式碼覆蓋率測試:asan/ubsan + gcov/gprof
    • 當然 二者也可分開編譯、測試。
    • ASAN 與 valgrind 不可同時啟用,執行 mtr 時如果新增--valgrind 引數,asan 會與 memcheck 衝突導致 crash,測試終止。

可選:

  • valgrind : 主要用於檢測記憶體問題,但執行速度很慢,更建議使用 ASAN。
  • 編譯選項:-DWITH_VALGRIND=1

持續跟蹤後續版本改進情況:

  • TSAN:MySQL 對其的支援尚不成熟。
  • MSAN:與 ASAN 功能重疊,且 MySQL 對其的支援尚不成熟。

參考連結:

llvm 工具集:

linux kernel 工具集:

MySQL:


歡迎關注我的微信公眾號【資料庫核心】:分享主流開源資料庫和儲存引擎相關技術。

歡迎關注公眾號資料庫核心
標題 網址
GitHub https://dbkernel.github.io
知乎 https://www.zhihu.com/people/dbkernel/posts
思否(SegmentFault) https://segmentfault.com/u/dbkernel
掘金 https://juejin.im/user/5e9d3ed251882538083fed1f/posts
CSDN https://blog.csdn.net/dbkernel
部落格園(cnblogs) https://www.cnblogs.com/dbkernel