mysql-server/storage/innobase/ut/ut0test.cc
2025-03-05 14:31:37 +07:00

775 lines
23 KiB
C++

/*****************************************************************************
Copyright (c) 2020, 2024, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License, version 2.0, as published by the
Free Software Foundation.
This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************/
#ifdef UNIV_DEBUG
#include "ut0test.h"
#include "btr0load.h"
#include "btr0mtib.h"
#include "buf0flu.h"
#include "ddl0impl-builder.h"
#include "dict0dd.h"
#include "dict0dict.h"
#include "fil0fil.h"
#include "scope_guard.h"
#define CALL_MEMBER_FN(object, ptrToMember) ((object).*(ptrToMember))
/** A macro to define a dispatch function or a command function. They all
have the same signature.
@param[in] func_ the function that is being declared. */
#define DISPATCH_FUNCTION_DEF(func_) \
/* @param[in] tokens the command line */ \
/* @return RET_PASS on success, or the error code. */ \
Ret_t func_(std::vector<std::string> &tokens) noexcept
namespace ib {
thread_local Tester tl_interpreter;
typedef Tester::Ret_t Ret_t;
#define DISPATCH(x) m_dispatch[#x] = &Tester::x
Tester::Tester() noexcept {
/* Kindly keep the commands in alphabetical order. */
DISPATCH(corrupt_ondisk_page0);
DISPATCH(corrupt_ondisk_root_page);
DISPATCH(count_page_type);
DISPATCH(count_used_and_free);
DISPATCH(dblwr_force_crash);
DISPATCH(find_fil_page_lsn);
DISPATCH(find_flush_sync_lsn);
DISPATCH(find_ondisk_page_type);
DISPATCH(find_root_page_no);
DISPATCH(find_space_id);
DISPATCH(find_tablespace_file_name);
DISPATCH(find_tablespace_physical_page_size);
DISPATCH(make_ondisk_root_page_zeroes);
DISPATCH(make_page_dirty);
DISPATCH(open_table);
DISPATCH(print_dblwr_has_encrypted_pages);
DISPATCH(print_tree);
}
void Tester::init() noexcept {
TLOG("Tester::init()");
std::ostringstream sout;
m_thd = current_thd;
XLOG("Initialization successfully completed");
set_output(sout);
}
void scan_page_type(space_id_t space_id,
std::map<page_type_t, page_no_t> &result_map) {
mtr_t mtr;
bool found;
const page_size_t page_size = fil_space_get_page_size(space_id, &found);
ut_ad(found);
fil_space_t *space = fil_space_acquire(space_id);
for (page_no_t page_no = 0; page_no < space->size; ++page_no) {
const page_id_t page_id(space_id, page_no);
mtr_start(&mtr);
buf_block_t *block =
buf_page_get(page_id, page_size, RW_S_LATCH, UT_LOCATION_HERE, &mtr);
page_type_t page_type = block->get_page_type();
result_map[page_type]++;
mtr_commit(&mtr);
}
fil_space_release(space);
}
DISPATCH_FUNCTION_DEF(Tester::count_page_type) {
std::ostringstream sout;
mtr_t mtr;
std::map<page_type_t, page_no_t> result_map;
TLOG("Tester::count_page_type()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "count_page_type");
const std::string space_name = tokens[1];
const space_id_t space_id = fil_space_get_id_by_name(space_name.c_str());
scan_page_type(space_id, result_map);
page_no_t total = 0;
for (auto it : result_map) {
sout << fil_get_page_type_str(it.first) << ": " << it.second << std::endl;
total += it.second;
}
sout << "Total: " << total << std::endl;
set_output(sout);
return RET_PASS;
}
DISPATCH_FUNCTION_DEF(Tester::count_used_and_free) {
std::ostringstream sout;
mtr_t mtr;
std::map<page_type_t, page_no_t> result_map;
TLOG("Tester::count_used_and_free()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "count_used_and_free");
const std::string space_name = tokens[1];
const space_id_t space_id = fil_space_get_id_by_name(space_name.c_str());
scan_page_type(space_id, result_map);
page_no_t total = 0;
for (auto it : result_map) {
total += it.second;
}
const page_no_t pages_free = result_map[FIL_PAGE_TYPE_ALLOCATED];
const page_no_t used = total - pages_free;
const double fill_factor = (used / (double)total) * 100;
const double free_factor = (pages_free / (double)total) * 100;
sout << "Total= " << total << ", used=" << used << ", free=" << pages_free
<< std::endl;
sout << "Fill factor= " << fill_factor << ", free factor= " << free_factor
<< std::endl;
set_output(sout);
return RET_PASS;
}
dict_table_t *Tester::is_table_open(
const std::string &table_name) const noexcept {
for (auto iter = m_open_tables.begin(); iter != m_open_tables.end(); ++iter) {
dict_table_t *table = *iter;
std::string tmp(table->name.m_name);
if (table_name == tmp) {
return table;
}
}
return nullptr;
}
Ret_t Tester::open_table(std::vector<std::string> &tokens) noexcept {
TLOG("Tester::open_table()");
ut_ad(tokens[0] == "open_table");
ut_ad(tokens.size() == 2);
std::ostringstream sout;
Ret_t ret;
std::string table_name = tokens[1];
dict_table_t *table = is_table_open(table_name);
if (table == nullptr) {
table = dict_table_open_on_name(table_name.c_str(), false, false,
DICT_ERR_IGNORE_NONE);
}
if (table == nullptr) {
XLOG("FAIL: Could not open table: " << table_name);
ret = RET_FAIL;
} else {
m_open_tables.push_back(table);
XLOG("PASS: Successfully opened table=" << table_name);
ret = RET_PASS;
}
set_output(sout);
return ret;
}
Ret_t Tester::find_space_id(std::vector<std::string> &tokens) noexcept {
TLOG("Tester::find_space_id");
ut_ad(tokens.size() == 2);
std::string table_name = tokens[1];
ut_ad(tokens[0] == "find_space_id");
dict_table_t *table = is_table_open(table_name);
ut_ad(table != nullptr);
space_id_t space_id = table->space;
dict_index_t *clust_index = table->first_index();
page_no_t root_page_no = clust_index->page;
TLOG("table_name=" << table_name << ", space_id=" << space_id
<< ", root_page_no=" << root_page_no);
{
std::ostringstream sout;
sout << space_id;
set_output(sout);
}
return RET_PASS;
}
Ret_t Tester::find_root_page_no(std::vector<std::string> &tokens) noexcept {
TLOG("Tester::find_root_page_no");
ut_ad(tokens[0] == "find_root_page_no");
ut_ad(tokens.size() == 2);
std::string table_name = tokens[1];
dict_table_t *table = is_table_open(table_name);
ut_ad(table != nullptr);
space_id_t space_id = table->space;
dict_index_t *clust_index = table->first_index();
page_no_t root_page_no = clust_index->page;
TLOG("table_name=" << table_name << ", space_id=" << space_id
<< ", root_page_no=" << root_page_no);
{
std::ostringstream sout;
sout << root_page_no;
set_output(sout);
}
return RET_PASS;
}
Ret_t Tester::find_fil_page_lsn(std::vector<std::string> &tokens) noexcept {
ut_ad(tokens[0] == "find_fil_page_lsn");
std::string space_id_str = tokens[1];
std::string page_no_str = tokens[2];
space_id_t space_id = std::stoul(space_id_str);
space_id_t page_no = std::stoul(page_no_str);
page_id_t page_id(space_id, page_no);
bool found;
page_size_t page_size = fil_space_get_page_size(space_id, &found);
ut_ad(found);
mtr_t mtr;
mtr_start(&mtr);
buf_block_t *block =
buf_page_get(page_id, page_size, RW_X_LATCH, UT_LOCATION_HERE, &mtr);
lsn_t newest_lsn = block->page.get_newest_lsn();
mtr_commit(&mtr);
std::ostringstream sout;
sout << newest_lsn;
set_output(sout);
return RET_PASS;
}
Ret_t Tester::find_ondisk_page_type(std::vector<std::string> &tokens) noexcept {
TLOG("Tester::find_ondisk_page_type()");
if (tokens.size() != 3) {
for (auto token : tokens) {
TLOG("TOKEN: " << token);
}
ut_ad(tokens.size() == 3);
}
ut_ad(tokens[0] == "find_ondisk_page_type");
const std::string space_id_str = tokens[1];
const std::string page_no_str = tokens[2];
const space_id_t space_id = std::stoul(space_id_str);
space_id_t page_no = std::stoul(page_no_str);
/* Calculate the offset here. */
bool found;
page_size_t page_size = fil_space_get_page_size(space_id, &found);
ut_ad(found);
const page_id_t page_id(space_id, page_no);
/* The buffer into which file page header is read. */
alignas(OS_FILE_LOG_BLOCK_SIZE) std::array<byte, OS_FILE_LOG_BLOCK_SIZE> buf;
fil_space_t *space = fil_space_get(space_id);
ut_ad(space != nullptr);
const fil_node_t *node = space->get_file_node(&page_no);
ut_ad(node->is_open);
const os_offset_t offset = page_no * page_size.physical();
/* When the space file is currently open we are not able to write to it
directly on Windows. We must use the currently opened handle. Moreover,
on Windows a file opened for the asynchronous access must be accessed only in
a way that allows asynchronous completion of the request. This requires that
the i/o buffer be aligned to OS block size and also its size divisible by OS
block size. */
IORequest read_io_type(IORequest::READ);
const dberr_t err = os_file_read(read_io_type, node->name, node->handle,
buf.data(), offset, OS_FILE_LOG_BLOCK_SIZE);
if (err != DB_SUCCESS) {
page_type_t page_type = fil_page_get_type(buf.data());
TLOG("Could not read page_id=" << page_id << ", page_type=" << page_type
<< ", err=" << err);
/* Since we do not pass encryption information in IORequest, if page is
encrypted on disk, it cannot be decrypted by i/o layer. But the encrypted
data will be available in the provided buffer. */
if (err == DB_IO_DECRYPT_FAIL) {
/* We expect this only for encrypted pages. For this function, this error
is OK because we will only read one header field and the header is not
encrypted. */
ut_ad(Encryption::is_encrypted_page(buf.data()));
} else {
return RET_FAIL;
}
}
const byte *page = buf.data();
const page_type_t type = fil_page_get_type(page);
const char *page_type = fil_get_page_type_str(type);
TLOG("page_type=" << type);
TLOG("page_type=" << page_type);
std::ostringstream sout;
sout << page_type;
set_output(sout);
return RET_PASS;
}
Ret_t Tester::find_tablespace_file_name(
std::vector<std::string> &tokens) noexcept {
std::ostringstream sout;
TLOG("Tester::find_tablespace_file_name()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "find_tablespace_file_name");
std::string space_name = tokens[1];
space_id_t space_id = fil_space_get_id_by_name(space_name.c_str());
char *filename = fil_space_get_first_path(space_id);
sout << filename;
set_output(sout);
ut::free(filename);
return RET_PASS;
}
DISPATCH_FUNCTION_DEF(Tester::find_tablespace_physical_page_size) {
std::ostringstream sout;
TLOG("Tester::find_tablespace_physical_page_size()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "find_tablespace_physical_page_size");
std::string space_name = tokens[1];
space_id_t space_id = fil_space_get_id_by_name(space_name.c_str());
bool found;
page_size_t page_size = fil_space_get_page_size(space_id, &found);
ut_ad(found);
sout << page_size.physical();
set_output(sout);
return RET_PASS;
}
DISPATCH_FUNCTION_DEF(Tester::make_ondisk_root_page_zeroes) {
TLOG("Tester::make_ondisk_root_page_zeroes()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "make_ondisk_root_page_zeroes");
const std::string table_name = tokens[1];
const dict_table_t *const table = is_table_open(table_name);
ut_ad(table != nullptr);
const dict_index_t *const clust_index = table->first_index();
const page_no_t root_page_no = clust_index->page;
const page_size_t page_size = dict_table_page_size(table);
return clear_page_prefix(table->space, root_page_no, page_size.physical());
}
Ret_t Tester::corrupt_ondisk_root_page(
std::vector<std::string> &tokens) noexcept {
std::ostringstream sout;
TLOG("Tester::corrupt_ondisk_root_page()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "corrupt_ondisk_root_page");
const std::string table_name = tokens[1];
const dict_table_t *table = is_table_open(table_name);
if (table == nullptr) {
std::vector<std::string> cmd{"open_table", table_name};
Ret_t st = open_table(cmd);
if (st != RET_PASS) {
XLOG("Failed to open table: " << table_name);
set_output(sout);
return st;
}
table = is_table_open(table_name);
}
if (table == nullptr) {
XLOG("Failed to open table: " << table_name);
set_output(sout);
return RET_FAIL;
}
const dict_index_t *const clust_index = table->first_index();
const page_no_t root_page_no = clust_index->page;
return clear_page_prefix(table->space, root_page_no, FIL_PAGE_DATA);
}
Ret_t Tester::clear_page_prefix(const space_id_t space_id, page_no_t page_no,
const size_t prefix_length) {
TLOG("Tester::clear_page_prefix()");
ut::aligned_array_pointer<byte, OS_FILE_LOG_BLOCK_SIZE> mem;
/* We read before write, as writes have to have length divisible by
OS_FILE_LOG_BLOCK_SIZE thus we need to learn the content of non-zeroed suffix.
Also, it's easier to spot errors during read than write, and this requires
reading at least the FIL_PAGE_DATA first bytes */
const auto buf_size =
ut_uint64_align_up(prefix_length, OS_FILE_LOG_BLOCK_SIZE);
mem.alloc(ut::Count(buf_size));
const page_id_t page_id{space_id, page_no};
fil_space_t *space = fil_space_get(space_id);
// Note: this call adjusts page_no, so it becomes relative to the node
fil_node_t *node = space->get_file_node(&page_no);
ut_ad(node->is_open);
const page_size_t page_size(space->flags);
const size_t page_size_bytes = page_size.physical();
ut_a(buf_size <= page_size_bytes);
// Note: we use updated page_no here, as os_aio needs offset relative to node
const os_offset_t offset = page_no * page_size_bytes;
/* When the space file is currently open we are not able to write to it
directly on Windows. We must use the currently opened handle. Moreover,
on Windows a file opened for the AIO access must be accessed only by AIO
methods. The AIO requires the operations to be aligned and with size
divisible by OS block size, so we first read block of the first page, to
corrupt it and write back. */
byte *buf = mem;
IORequest read_io_type(IORequest::READ);
dberr_t err = os_file_read(read_io_type, node->name, node->handle, buf,
offset, buf_size);
if (err != DB_SUCCESS) {
page_type_t page_type = fil_page_get_type(buf);
TLOG("Could not read page_id=" << page_id << ", page type=" << page_type
<< ", err=" << err);
if (err == DB_IO_DECRYPT_FAIL) {
/* We expect this only for encrypted pages. Since this function doesn't
actually read (or use) the contents, this error is OK. */
ut_ad(Encryption::is_encrypted_page(buf));
} else {
return RET_FAIL;
}
}
ut_ad(prefix_length <= buf_size);
memset(buf, 0x00, prefix_length);
IORequest write_io_type(IORequest::WRITE);
err = os_file_write(write_io_type, node->name, node->handle, buf, offset,
buf_size);
if (err == DB_SUCCESS) {
TLOG("Successfully zeroed prefix of page_id=" << page_id << ", prefix="
<< prefix_length);
} else {
TLOG("Could not write zeros to page_id=" << page_id << ", err=" << err);
return RET_FAIL;
}
return RET_PASS;
}
DISPATCH_FUNCTION_DEF(Tester::dblwr_force_crash) {
TLOG("Tester::dblwr_force_crash()");
ut_ad(tokens.size() == 3);
ut_ad(tokens[0] == "dblwr_force_crash");
std::string space_id_str = tokens[1];
std::string page_no_str = tokens[2];
space_id_t space_id = std::stoul(space_id_str);
space_id_t page_no = std::stoul(page_no_str);
page_id_t page_id(space_id, page_no);
dblwr::Force_crash = page_id;
return RET_PASS;
}
Ret_t Tester::corrupt_ondisk_page0(std::vector<std::string> &tokens) noexcept {
TLOG("Tester::corrupt_ondisk_page0()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "corrupt_ondisk_page0");
const std::string table_name = tokens[1];
const dict_table_t *const table = is_table_open(table_name);
ut_ad(table != nullptr);
return clear_page_prefix(table->space, 0, FIL_PAGE_DATA);
}
DISPATCH_FUNCTION_DEF(Tester::make_page_dirty) {
TLOG("Tester::make_page_dirty()");
ut_ad(tokens.size() == 3);
ut_ad(tokens[0] == "make_page_dirty");
mtr_t mtr;
std::string space_id_str = tokens[1];
std::string page_no_str = tokens[2];
space_id_t space_id = std::stoul(space_id_str);
space_id_t page_no = std::stoul(page_no_str);
page_id_t page_id(space_id, page_no);
fil_space_t *space = fil_space_acquire_silent(space_id);
if (space == nullptr) {
return RET_FAIL;
}
if (page_no > space->size) {
fil_space_release(space);
return RET_FAIL;
}
mtr.start();
buf_block_t *block = buf_page_get(page_id, page_size_t(space->flags),
RW_X_LATCH, UT_LOCATION_HERE, &mtr);
if (block != nullptr) {
byte *page = block->frame;
page_type_t page_type = fil_page_get_type(page);
/* Don't dirty a page that is not yet used. */
if (page_type != FIL_PAGE_TYPE_ALLOCATED) {
ib::info(ER_IB_MSG_574)
<< "Dirtying page: " << page_id
<< ", page_type=" << fil_get_page_type_str(page_type);
mlog_write_ulint(page + FIL_PAGE_TYPE, page_type, MLOG_2BYTES, &mtr);
}
}
mtr.commit();
mtr.wait_for_flush();
fil_space_release(space);
if (block != nullptr) {
buf_flush_sync_all_buf_pools();
}
return RET_PASS;
}
DISPATCH_FUNCTION_DEF(Tester::print_tree) {
TLOG("Tester::print_tree()");
ut_ad(tokens.size() == 2);
ut_ad(tokens[0] == "print_tree");
std::string table_name = tokens[1];
dict_table_t *table = dict_table_open_on_name(table_name.c_str(), false,
false, DICT_ERR_IGNORE_NONE);
if (table == nullptr) {
TLOG("Could not open table: " << table_name);
return RET_FAIL;
}
auto guard =
create_scope_guard([table]() { dict_table_close(table, false, false); });
const dict_index_t *clust_index = table->first_index();
BFT::Callback cb;
BFT bft(clust_index, cb);
bft.traverse();
TLOG(cb);
return RET_PASS;
}
Ret_t Tester::print_dblwr_has_encrypted_pages(
std::vector<std::string> &) noexcept {
std::ostringstream sout;
if (dblwr::has_encrypted_pages()) {
std::string m1("Double write file has encrypted pages.");
TLOG(m1);
sout << m1;
} else {
std::string m1("Double write file has NO encrypted pages.");
TLOG(m1);
sout << m1;
}
set_output(sout);
return RET_PASS;
}
void Tester::close_table(dict_table_t *table) noexcept {
for (auto iter = m_open_tables.begin(); iter != m_open_tables.end(); ++iter) {
dict_table_t *tmp = *iter;
if (tmp == table) {
m_open_tables.erase(iter);
break;
}
}
dict_table_close(table, false, false);
}
void Tester::destroy() noexcept {
for (auto iter = m_open_tables.begin(); iter != m_open_tables.end(); ++iter) {
dict_table_t *table = *iter;
dict_table_close(table, false, false);
}
m_open_tables.clear();
}
Ret_t Tester::find_flush_sync_lsn(std::vector<std::string> &tokens) noexcept {
ut_ad(tokens.size() == 1);
ut_ad(tokens[0] == "find_flush_sync_lsn");
std::ostringstream sout;
sout << get_flush_sync_lsn();
set_output(sout);
return RET_PASS;
}
Ret_t Tester::run(const std::string &cmdline) noexcept {
Ret_t ret = RET_PASS;
std::vector<std::string> tokens;
std::istringstream sin(cmdline);
while (!sin.eof()) {
std::string token;
std::getline(sin, token, ' ');
tokens.push_back(token);
}
std::string command = tokens[0];
/* Save the current command token. This will be the value of the
innodb_interpreter variable. */
m_command = command;
m_thd = current_thd;
if (command == "init") {
/* do nothing. */
} else if (command == "destroy") {
destroy();
} else if (command == "buf_flush_sync_all_buf_pools") {
buf_flush_sync_all_buf_pools();
TLOG("Executed buf_flush_sync_all_buf_pools()");
ret = RET_PASS;
} else if (command == "bulk_load_enable_slow_io") {
Btree_multi::bulk_load_enable_slow_io_debug();
} else if (command == "bulk_load_disable_slow_io") {
Btree_multi::bulk_load_disable_slow_io_debug();
} else {
ret = RET_CMD_TBD;
}
/* Second approach. */
if (ret == RET_CMD_TBD) {
auto cmd = m_dispatch.find(command);
if (cmd != m_dispatch.end()) {
ret = CALL_MEMBER_FN(*this, cmd->second)(tokens);
} else {
ret = RET_CMD_TBD;
}
}
if (ret == RET_CMD_TBD) {
ret = RET_FAIL;
}
return ret;
}
void Tester::update_thd_variable() noexcept {
char **output = thd_innodb_interpreter_output(m_thd);
*output = const_cast<char *>(m_log.c_str());
char **output2 = thd_innodb_interpreter(m_thd);
*output2 = const_cast<char *>(m_command.c_str());
}
void Tester::set_output(const std::ostringstream &sout) noexcept {
m_log = sout.str();
}
void Tester::set_output(const std::string &log) noexcept { m_log = log; }
void Tester::clear_output() noexcept { m_log = ""; }
void Tester::append_output(const std::string &log) noexcept { m_log += log; }
int interpreter_run(const char *command) noexcept {
return (int)tl_interpreter.run(command);
}
} // namespace ib
void ib_interpreter_update(MYSQL_THD thd [[maybe_unused]],
SYS_VAR *var [[maybe_unused]],
void *var_ptr [[maybe_unused]],
const void *save [[maybe_unused]]) {
TLOG("ib_interpreter_update");
/* Point the THD variables - innodb_interpreter and innodb_interpreter_output
to the correct values. */
ib::tl_interpreter.update_thd_variable();
}
int ib_interpreter_check(THD *thd [[maybe_unused]],
SYS_VAR *var [[maybe_unused]], void *save,
struct st_mysql_value *value) {
TLOG("ib_interpreter_check");
char buff[STRING_BUFFER_USUAL_SIZE];
int len = sizeof(buff);
ut_a(save != nullptr);
ut_a(value != nullptr);
const char *cmd = value->val_str(value, buff, &len);
int ret = ib::interpreter_run(cmd ? cmd : "");
TLOG("ib_interpreter_check() is returning: " << ret);
*static_cast<const char **>(save) = cmd;
return ret;
}
#endif /* UNIV_DEBUG */