特性介绍 | MySQL 测试框架 MTR 系列教程(二):进阶篇 - 内存/线程/代码覆盖率/单元/压力测试

Sanitizer 工具了解:功能与使用指南 即时中断:在未定义行为发生时立即终止程序精确报告:显示错误类型和上下文(如整数溢出的具体运算)工具核心价值最佳使用场景ASan全面内存错误检测x86 开发/模拟器深度测试HWASanARM64 低开销内存检测Android 真机测试UBSan未定义行为捕获所有平台基础安全检查TSan数据竞争检测多线程程序调试MSan未初始化内存检测安全关键代码(需全链路编译)LSan内存泄漏检测与 ASan 集成使用终极建议Android 开发者:HWASan 是 ASan 的真机替代方案。 阅读详情

作者:卢文双 资深数据库内核研发

序言:

以前对 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 value。Enable 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-valgrind、default.push-valgrind、default.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包括KASAN和KMSAN

Sanitizers 项目本是 LLVM 项目的一部分,但 GNU 也将该系列工具加入到了自家的 GCC 编译器中(clang 当然也支持)。

  • GCC 4.8 版本开始支持 Address Sanitizer和 Thread Sanitizer。
  • GCC 4.9 版本开始支持 Leak Sanitizer 和 UndefinedBehaviorSanitizer。

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 test或make 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_gtidsASAN 与 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。
压力测试否只要编译,就会产生 stress、innodb_stress 两个 suite 。 mysql-stress-test.pl 需要自定义 初始化和运行的 SQL 语句,不建议使用。测试成功。正常运行 mtr 全量 suite 或单独运行 stress、innodb_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 test或make 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:


欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

标题网址
GitHubhttps://dbkernel.github.io
知乎https://www.zhihu.com/people/dbkernel/posts
思否(SegmentFault)https://segmentfault.com/u/dbkernel
掘金https://juejin.im/user/5e9d3ed251882538083fed1f/posts
优快云https://blog.youkuaiyun.com/dbkernel
博客园(cnblogs)https://www.cnblogs.com/dbkernel
开发者测试笔记08--ASAN、TSAN、UBSAN、KASAN使用总结 参考链接 https://blog.youkuaiyun.com/hanlizhong85/article/details/78076668 阅读详情

相关推荐

第14章_MySQL事务日志

✅作者简介:大家好,我是Cisyam,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉💞当前专栏:✨特色专栏:🥭本文内容:MySQL笔记:B站康师傅天花板教程(持续更新),欢迎大家访问📚个人知识库:,欢迎大家访问事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?锁机制重做日志回滚日志有的DBA或许会认为 UNDO 是 REDO 的逆过程,其实不然。REDO 和 UNDO都可以视为是一种恢复操作"物理级别"逻辑操作事务的回滚逆操作。

分享思想,留下痕迹。 985

源码分析 | MySQL测试框架 MTR 系列教程(三):源码篇

作者:卢文双 资深数据库内核研发 以前对 MySQL 测试框架 MTR 的使用,主要集中于 SQL 正确性验证。近期由于工作需要,深入了解了 MTR 的方方面面,发现 MTR 的能力不仅限于此,还支持单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁等功能,因此,本着分享的精神,将其总结成一个系列。 本文是第三篇源码篇。

dbkernel 的博客 1222

MySQL中MTR的概念

MTR全称是Mini-Transaction,顾名思义,可以理解为”最小的事务”,MySQL中把对底层页面的一次原子访问的过程称之为一个Mini-Transaction,这里的原子操作,指的是要么全部成功,要么全部失败,不存在中间状态。     MTR主要是被用在写undo log和redo log的场景下的。例如,我们要向一个B+树索引中插入一条记录,此时要么插入成功,要么插入失败,这个过程就可以称为一个MTR过程,这个过程中会产生一组redo log日志,这组日志在做MySQL的崩溃恢复的时候,是一个不可分割的整体。     假如我们有一个事务,事务中包含3条语句,那么MTR的概念图如下:

特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇

作者:卢文双 资深数据库内核研发 以前对 MySQL 测试框架 MTR 的使用,主要集中于 SQL 正确性验证。近期由于工作需要,深入了解了 MTR 的方方面面,发现 MTR 的能力不仅限于此,还支持单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁等功能,因此,本着分享的精神,将其总结成一个系列。 主要内容如下: 入门篇:机制、编译安装、参数、指令、推荐用法、添加 case、异常调试 进阶篇:高阶用法,包括单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁 源码篇 语法篇

dbkernel 的博客 1095

STM32G4实战:用CubeMX+Keil五分钟搞定FreeRTOS多任务LED控制(避坑指南)

本文详细介绍了如何在STM32G4系列芯片上使用CubeMX和Keil快速配置FreeRTOS实现多任务LED控制,包括环境准备、时钟配置、任务创建及常见问题解决方案。重点解决了时钟源冲突等高频问题,并提供了优化建议和进阶应用示例,帮助开发者高效完成嵌入式开发任务。

joy55的博客 227

[南大ICS-PA2] 字符串处理函数和printf实现

[南大ICS-PA2] 程序、运行时环境与AM 字符串处理函数和printf实现

qq_42474104的博客 2987

MySQL InnoDB 日志管理机制中的MTR和日志刷盘

参数解释(部分内容个人理解,特别是我将log file 分为os cache 和 磁盘2种,更多内容还要求证。0:每次事务提交时,根本不会去刷日志缓冲区。log buffer将每秒一次地写入到OS cache的log file中,并且log file的flush(刷到磁盘)上的Log Files操作同时进行。1:每次事务提交时MySQL都会把log buffer的数据写入到OS cache的log file,并且flush(刷到磁盘)Log Files中去,该模式为系统默认。

hongjian006的博客 606

从QEvent到系统响应:EC如何成为硬件事件的隐形信使

本文深入解析嵌入式控制器(EC)如何作为硬件事件的隐形信使,通过QEvent机制将按键、合盖等物理事件转换为SCI信号,并经由ACPI架构触发操作系统响应。EC与BIOS紧密协作,实现了从底层硬件到系统层面的高效事件传递,是嵌入式开发和系统优化的关键技术。

yellow的专栏 652

MySQL数据库之InnoDB存储引擎之MTR

1 MTR(mini-transaction) 微事务 在MySQL的 InnoDB日志管理机制中,有一个很重要的概念就是MTR。 MTR是InnoDB存储擎中一个很重要的用来保证物理写的完整性和持久性的机制。 2 MTR在MysQL架构中的位置 MTR是上面的逻辑层与下面物理层的交互窗口 3 MTR作用 主要保证物理操作的一致性和原子性; 同时也是用来保证下层物理数据正确性、完整性及持久性的机制。 ...

数据库技术 964

vue3:最新实现腾讯人脸核身+增强版人脸核身使用方法及示例源码,Vue3如何使用腾讯云慧眼人脸核身,提供人脸核身案例、身份信息核验、活体检测与核身比对等示例代码(后端spring与thinkphp)

vue3接入腾讯人脸核身,腾讯云人脸核身Vue3+后端代码,H5人脸核身,vue3 H5如何使用腾讯人脸核身sdk,vue3+thinkphp腾讯人脸核身,Spring Boot接入腾讯云人脸核身教程,hinkphp腾讯人脸核身,vue3人脸核身插件接入流程,vue+spring boot实现腾讯人脸核身,vue3接入腾讯云【增强版人脸核身接入】,vue3 h5微信公众号网页腾讯云慧眼人脸核身,tvue3接入人脸核身SDK,vue3+typescrip+vite接入慧眼实名认证,vue3腾讯云活体人脸核身(

✅ 精选推荐文章 4121

在线考试题库抓取:Python 爬虫抓取各类在线考试平台题库和试题

通过这些技术,您可以抓取各类在线考试平台上的题库数据,进行数据分析、生成练习题集,甚至用于考试准备。为了演示抓取过程,本篇博客选择了两个常见的在线考试平台进行数据抓取:一个是教育考试平台(如“中小学题库”),另一个是职业考试平台(如“华图教育”)。对于这些网站,我们需要找到包含题库信息的页面,并分析每一页的 HTML 结构,从而定位到题目、选项、答案等数据。大多数在线考试平台的题库是分页显示的,因此我们需要遍历多个页面,抓取所有的题库数据。因此,我们需要设置合适的请求头来模拟真实的浏览器请求。

2201_76125261的博客 3445

特性介绍 | MySQL测试框架 MTR 系列教程(四):语法篇

作者:卢文双 资深数据库内核研发 以前对 MySQL 测试框架 MTR 的使用,主要集中于 SQL 正确性验证。近期由于工作需要,深入了解了 MTR 的方方面面,发现 MTR 的能力不仅限于此,还支持单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁等功能,因此,本着分享的精神,将其总结成一个系列。 本文是第四篇语法篇。

dbkernel 的博客 1199

Python库CloudScraper详细使用(绕过 Cloudflare 的反机器人页面的 Python 模块)

CloudScraper是一个专门用于绕过Cloudflare反机器人保护的Python库,它基于requests库构建,能够处理Cloudflare的5秒盾、WAF和人机验证等防护机制。

数据知道的博客 1万+

mysql的mtr是指什么_MySQL中MTR的概念

MySQL中MTR的概念作者: AsiaYe  更新时间:2020-11-10 10:45:39  原文链接MTR全称是Mini-Transaction,顾名思义,可以理解为"最小的事务",MySQL中把对底层页面的一次原子访问的过程称之为一个Mini-Transaction,这里的原子操作,指的是要么全部成功,要么全部失败,不存在中间状态。MTR主要是被用在写undo log和redo log的...

weixin_36063465的博客 725

Altera FPGA与高速ADS4249和DAC3482的LVDS接口设计

引言:本文以TI的ADS4249(ADC)和DAC3482(DAC)之间的接口为例,介绍Altera FPGA与ADC/DAC之间的DDR LVDS接口设计以及时序约束详细设计。本文介绍的实例可方便扩展到具有类似接口格式的其他高速数据转换器设计。

FPGA_Designer 1902

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

mysql的mtr是指什么_关于innodb mtr模块

mtr (mini-transaction)微事务mtr作用mtr模块主要保证物理操作的一致性和原子性1 一致性:通过读写锁来保证2 原子性:涉及到的物理更新,都记入redo日志mtr何时使用1 文件管理:innodb表空间以segment,extent,page的方式管理,segment,extent,page的申请释放,都会修改对应的控制信息。如fseg_alloc_free_page_gen...

weixin_30823291的博客 683

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

海山数据库(He3DB)源码详解:海山Mysql 自动测试框架MTR

在修改内核代码后,不仅需要测试新增功能,同时也要对原有功能做回归测试,以保证新加代码对原有功能没有影响,这就需要用到MySQL源码自带的测试框架,简称MTR。主要应用于MySQL等相关数据库项目开发测试,对单个功能点或者模块进行正确性和功能性测试,该工具在SQL级别对MySQL进行单元测试。不仅仅能测试SQL语句,也能根据实际需要测试不同启动参数配置的MySQL服务器和客户端从而验证指定的功能逻辑。

于巍,花名漠雪,移动云数据库技术委员会主任,海山数据库研发负责人,PG中文社区常委,PG中文社区海外联络人。15年数据库内核研发经验,4年美国、以色列等海外研发经历,华为GaussDB、阿里云PolarDB、移动云He3DB等核心架构 784

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

mysql的测试方案

mysql测试方案

jsl80215219的博客 3450

mysql-test【安装使用篇】03:mysqltest命令和mtr命令的区别

mysql-test【安装使用篇】03:mysqltest命令和mtr命令的区别

weixin_40612128的博客 884

接口自动化测试做到什么程度的覆盖算是合格的

集成测试覆盖应达到80%以上,这不仅包括直接接口间的调用,还包括接口在不同服务或系统间的集成情况,以发现集成层面的潜在问题,减少系统级故障。综上,接口自动化测试的目标不仅仅是追求覆盖率的数量,更重要的是覆盖的质量、测试的可持续性和对系统变化的适应能力。长期投资回报:虽然提高测试的可维护性和扩展性、管理测试数据需要初期投入,但从长远来看,它能够极大减少测试的总体成本,提升测试效率,加速产品迭代速度,带来更高的投资回报率。时,应充分考虑这些边缘情况,至少覆盖80%以上的边界和异常测试场景,以确保系统的鲁棒性。

OKCRoss的博客 1241

MySQL Test Run (Mysql MTR)

MySQL Test Run (Mysql MTR)

lixiaomei0623的专栏 1285

MySQL Test Run 测试框架介绍

包含.inc文件,用于测试开始时判断是否满足测试条件,在测试用例中通过source命令引入,就像C/C++的头文件,在mtr的体系中,我们可以将许多可重复利用的测试语句,提取到*.inc文件中,放到对应的include目录中,以便在*.test中直接调用。主要测试步骤,是通过执行一个case(*.test)中的语句(包括sql语句和其他管理语句),将case的输出记录保存在结果文件(*.result)中,或者是将语句执行结果与标准的输出结果作diff,如果出现偏差就会报错。

软件测试技术分享官 492

mysql 内存越界_asan(AddressSanitizer)内存诊断(越界,非法访问等)工具使用—一款比valgrind更好用的工具...

操作系统:centos8gcc:8.3.1asan是gcc原生的内存诊断工具,可以诊断内存越界,非法访问,内存泄漏,内存double free等常见内存问题,并且效率比valgrind高好几倍,可以克服valgrind的一些问题,比如占用内存高的问题,是一款比valgrind更好用的内存问题分析工具!1.准备工作:1.在编译环境上安装asan的开发库yum install libasan.x86_...

weixin_36008052的博客 588

mysql单元测试

embedded-db-junit:JUnit Rule for providing an embedded in-memory database for your tests mysql-connector-mxj:MySQL Connector/MXJ is a Java Utility package for deploying and managing a MySQL database. h2:H2 is an embeddable RDBMS written in Java. 使用H2..

阿拉斯加大闸蟹的博客 2806

MySQL-Test-Run测试工具

MTR测试工具

weixin_44534229的博客 4945
上一篇: 特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇
下一篇: 源码分析 | MySQL测试框架 MTR 系列教程(三):源码篇
dbkernel
博客等级 码龄4年 60粉丝 42原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值