/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved. 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 also distributed 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/PolarDB-X Engine hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL/PolarDB-X Engine. 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 */ #include "my_murmur3.h" // my_murmur3_32 #include "my_sys.h" // MY_WME #include "mysql/components/services/psi_mutex_bits.h" // PSI_mutex_key #include "mysql/components/services/psi_rwlock_bits.h" // PSI_rwlock_key #include "mysql/psi/mysql_memory.h" #include "mysql/psi/mysql_mutex.h" #include "mysql/psi/mysql_rwlock.h" #include "mysql/service_mysql_alloc.h" // my_malloc #include "sql/auth/auth_acls.h" // SELECT_ACL #include "sql/auth/auth_common.h" // check_access #include "sql/field.h" // store #include "sql/handler.h" // update_statistics #include "sql/log.h" #include "sql/psi_memory_key.h" #include "sql/table.h" #include "sql/sql_show.h" #include "sql/sql_statistics.h" bool opt_tablestat = true; bool opt_indexstat = true; /* Max retry times when generate object stats */ static uint object_statistics_retry_max_times = 10; #ifdef HAVE_PSI_INTERFACE static PSI_memory_key key_memory_object_stats; static PSI_rwlock_key key_LOCK_object_stats; #endif ST_FIELD_INFO table_stats_fields_info[] = { {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema", 0}, {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name", 0}, {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read", 0}, {"ROWS_CHANGED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed", 0}, {"ROWS_CHANGED_X_INDEXES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed_x_#indexes", 0}, {"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_inserted", 0}, {"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_deleted", 0}, {"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_updated", 0}, {"ROWS_READ_DELETE_MARK", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read_delete_mark", 0}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}}; ST_FIELD_INFO index_stats_fields_info[] = { {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema", 0}, {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name", 0}, {"INDEX_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Index_name", 0}, {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Rows_read", 0}, {"SCAN_USED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Scan_used", 0}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}}; /** We want to accumulate the memory, so define a new allocator. */ void *String_stats_alloc::operator()(size_t s) const { return my_malloc(key_memory_object_stats, s, MYF(MY_WME | ME_FATALERROR)); } /** If String_stats_type is treated as key, here should realize the hash function. the std::hash template specialization. */ namespace std { size_t hash::operator()(const String_stats_type &s) const { return murmur3_32(reinterpret_cast(s.c_str()), s.size(), 0); } } // namespace std; template String_stats_type::~basic_string(); /** Allocate and init a new Object_stats. @retval Object_stats New allocated object */ template T *object_stats_allocator() { void *ptr = nullptr; T *stats = nullptr; ptr = my_malloc(key_memory_object_stats, sizeof(T), MYF(MY_WME | ME_FATALERROR)); if (ptr) stats = new (ptr) T(); return stats; } Table_stats::Table_stats() { rows_read = 0; rows_changed = 0; rows_deleted = 0; rows_inserted = 0; rows_updated = 0; rows_changed_x_indexes = 0; rds_rows_read_del_mark = 0; } /** Accumulate the data from Stats_data; @param[in] data Stats_data @param[in] key_number How many keys */ void Table_stats::accumulate(Stats_data &data, size_t key_number) { rows_read += data.rows_read; rows_changed += data.rows_changed; rows_deleted += data.rows_deleted; rows_inserted += data.rows_inserted; rows_updated += data.rows_updated; rows_changed_x_indexes += data.rows_changed * (key_number ? key_number : 1); rds_rows_read_del_mark += data.rds_rows_read_del_mark; } Index_stats::Index_stats() { rows_read = 0; scan_used = 0; } /** Accumulate the data from Stats_data; @param[in] data Stats_data @param[in] key_number Which key */ void Index_stats::accumulate(Stats_data &data, size_t key_number) { if (key_number < MAX_KEY) rows_read += data.index_rows_read[key_number]; if (key_number < MAX_KEY) scan_used += data.index_scan_used[key_number]; } /* Table_stats template specialization */ template Table_stats *object_stats_allocator(); /* Index_stats template specialization */ template Index_stats *object_stats_allocator(); /** Constructor of Object stats cache */ Object_stats_cache::Object_stats_cache() : m_table_map(key_memory_object_stats), m_index_map(key_memory_object_stats) { init(); } /** The singleton instance entry. */ Object_stats_cache *Object_stats_cache::instance() { static Object_stats_cache cache; return &cache; } void Object_stats_cache::init() { mysql_rwlock_init(key_LOCK_object_stats, &m_table_lock); mysql_rwlock_init(key_LOCK_object_stats, &m_index_lock); } Object_stats_cache::~Object_stats_cache() {} void Object_stats_cache::destroy() { mysql_rwlock_destroy(&m_table_lock); mysql_rwlock_destroy(&m_index_lock); destroy_map(map()); destroy_map(map()); } /** Accumulate the table statistics @param[in] table TABLE object @param[in] data stats_data reference */ void Object_stats_cache::update_table_stats(TABLE_SHARE *share) { Stats_data &data = share->stats_data; Object_stats *stats = nullptr; if ((!data.rows_read && !data.rows_changed && !data.rds_rows_read_del_mark) || !share->db.str || !share->table_name.str) return; String_stats_type key(share->table_cache_key.str, share->table_cache_key.length); RWlock_helper helper(&m_table_lock, false); DBUG_ASSERT(helper.effect()); uint retrys = 0; retry: if (retrys++ > object_statistics_retry_max_times) { sql_print_warning("Table statistics retry %d times.", retrys); return; } helper.rdlock(); if (!(stats = lookup(key))) { helper.unlock(); helper.wrlock(); std::pair res = generate_stats(key); if (!res.second) { helper.unlock(); goto retry; } else { stats = res.first; } } /* TODO: Move it later if support flushing in future. */ helper.unlock(); if (stats) { stats->accumulate(data, share->keys); data.reset_table(); } } /** Accumulate the index statistics @param[in] table TABLE object @param[in] data stats_data reference */ void Object_stats_cache::update_index_stats(TABLE_SHARE *share) { char buf[NAME_LEN * 3 + 3]; size_t length; KEY *info; Object_stats *stats = nullptr; Stats_data &data = share->stats_data; for (size_t i = 0; i < share->keys; i++) { stats = nullptr; if (data.index_rows_read[i] > 0) { info = share->key_info + i; length = snprintf(buf, NAME_LEN * 3 + 3, "%s%c%s%c%s", share->db.str, '\0', share->table_name.str, '\0', info->name); String_stats_type key(buf, length); RWlock_helper helper(&m_index_lock, false); DBUG_ASSERT(helper.effect()); uint retrys = 0; retry: if (retrys++ > object_statistics_retry_max_times) { sql_print_warning("Index statistics retry %d times.", retrys); return; } helper.rdlock(); if (!(stats = lookup(key))) { helper.unlock(); helper.wrlock(); std::pair res = generate_stats(key); if (!res.second) { helper.unlock(); goto retry; } else { stats = res.first; } } /* TODO: Move it later if support flushing in future. */ helper.unlock(); if (stats) { stats->accumulate(data, i); data.reset_index(i); } } } } /** Fill the statistics into table_statisitcs table. @param[in] thd User connection context @param[in] table Current opened schema table */ int Object_stats_cache::fill_table_stats(THD *thd, TABLE *table) { const char *table_schema; const char *table_name; RWlock_helper helper(&m_table_lock, true); DBUG_ASSERT(helper.effect()); helper.rdlock(); for (auto it = map().cbegin(); it != map().cend(); it++) { restore_record(table, s->default_values); table_schema = (it->first).c_str(); table_name = table_schema + strlen(table_schema) + 1; const Table_stats *table_stats = it->second; TABLE_LIST tmp_table; memset((char *)&tmp_table, 0, sizeof(tmp_table)); tmp_table.table_name = table_name; tmp_table.db = table_schema; tmp_table.grant.privilege = 0; if (check_access(thd, SELECT_ACL, tmp_table.db, &tmp_table.grant.privilege, 0, 0, is_infoschema_db(table_schema)) || check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1)) continue; table->field[0]->store(table_schema, strlen(table_schema), system_charset_info); table->field[1]->store(table_name, strlen(table_name), system_charset_info); table->field[2]->store((longlong)table_stats->rows_read, true); table->field[3]->store((longlong)table_stats->rows_changed, true); table->field[4]->store((longlong)table_stats->rows_changed_x_indexes, true); table->field[5]->store((longlong)table_stats->rows_inserted, true); table->field[6]->store((longlong)table_stats->rows_deleted, true); table->field[7]->store((longlong)table_stats->rows_updated, true); table->field[8]->store((longlong)table_stats->rds_rows_read_del_mark, true); if (schema_table_store_record(thd, table)) return 1; } return 0; } /** Fill the statistics into index_statisitcs table. @param[in] thd User connection context @param[in] table Current opened schema table */ int Object_stats_cache::fill_index_stats(THD *thd, TABLE *table) { const char *table_schema; const char *table_name; const char *index_name; RWlock_helper helper(&m_index_lock, true); DBUG_ASSERT(helper.effect()); helper.rdlock(); for (auto it = map().cbegin(); it != map().cend(); it++) { restore_record(table, s->default_values); table_schema = (it->first).c_str(); table_name = table_schema + strlen(table_schema) + 1; index_name = table_name + strlen(table_name) + 1; const Index_stats *index_stats = it->second; TABLE_LIST tmp_table; memset((char *)&tmp_table, 0, sizeof(tmp_table)); tmp_table.table_name = table_name; tmp_table.db = table_schema; tmp_table.grant.privilege = 0; if (check_access(thd, SELECT_ACL, tmp_table.db, &tmp_table.grant.privilege, 0, 0, is_infoschema_db(table_schema)) || check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1)) continue; table->field[0]->store(table_schema, strlen(table_schema), system_charset_info); table->field[1]->store(table_name, strlen(table_name), system_charset_info); table->field[2]->store(index_name, strlen(index_name), system_charset_info); table->field[3]->store((longlong)index_stats->rows_read, true); table->field[4]->store((longlong)index_stats->scan_used, true); if (schema_table_store_record(thd, table)) return 1; } return 0; } void handler::update_table_statistics() { if (!table || !table->s || (!stats_data.rows_read && !stats_data.rows_changed && !stats_data.rds_rows_read_del_mark) || !table->s->db.str || !table->s->table_name.str) return; table->s->stats_data.rows_read += stats_data.rows_read; table->s->stats_data.rows_changed += stats_data.rows_changed; table->s->stats_data.rows_inserted += stats_data.rows_inserted; table->s->stats_data.rows_deleted += stats_data.rows_deleted; table->s->stats_data.rows_updated += stats_data.rows_updated; table->s->stats_data.rds_rows_read_del_mark += stats_data.rds_rows_read_del_mark; } void handler::update_index_statistics() { if (!table || !table->s || !table->s->table_cache_key.str || !table->s->table_name.str) return; for (uint x = 0; x < table->s->keys; ++x) { if (stats_data.index_rows_read[x]) { table->s->stats_data.index_rows_read[x] += stats_data.index_rows_read[x]; table->s->stats_data.index_scan_used[x] ++; } } } /** Accumulate the operated rows once statement end. */ void handler::update_statistics() { if (opt_tablestat) update_table_statistics(); if (opt_indexstat) update_index_statistics(); stats_data.reset(); } /** Send the table stats back to the client. */ int fill_schema_table_stats(THD *thd, TABLE_LIST *tables, Item *__attribute__((unused))) { TABLE *table = tables->table; DBUG_ENTER("fill_schema_table_stats"); mysql_mutex_lock(&LOCK_open); for (const auto &key_and_value : *table_def_cache) { TABLE_SHARE *share = key_and_value.second.get(); Object_stats_cache::instance()->update_table_stats(share); } mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(Object_stats_cache::instance()->fill_table_stats(thd, table)); } /** Send the index stats back to the client. */ int fill_schema_index_stats(THD *thd, TABLE_LIST *tables, Item *__attribute__((unused))) { TABLE *table = tables->table; DBUG_ENTER("fill_schema_index_stats"); mysql_mutex_lock(&LOCK_open); for (const auto &key_and_value : *table_def_cache) { TABLE_SHARE *share = key_and_value.second.get(); Object_stats_cache::instance()->update_index_stats(share); } mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(Object_stats_cache::instance()->fill_index_stats(thd, table)); } #ifdef HAVE_PSI_INTERFACE static PSI_rwlock_info object_statistics_rwlocks[] = { {&key_LOCK_object_stats, "LOCK_sql_statistics", PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME}}; static PSI_memory_info object_statistics_memory[] = { {&key_memory_object_stats, "object_statistics", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME}}; static void init_object_statistics_psi_keys() { const char *category = "sql"; int count; count = static_cast(array_elements(object_statistics_rwlocks)); mysql_rwlock_register(category, object_statistics_rwlocks, count); count = static_cast(array_elements(object_statistics_memory)); mysql_memory_register(category, object_statistics_memory, count); } #endif /** Init the object statistics context when booting, since the singleton instance is not thread safe. */ void object_statistics_context_init() { #ifdef HAVE_PSI_INTERFACE init_object_statistics_psi_keys(); #endif Object_stats_cache::instance(); } void object_statistics_context_destroy() { Object_stats_cache::instance()->destroy(); }