mariadb/storage/myisam/myisamlog.c
Michael Widenius a260b15554 MDEV-4011 Added per thread memory counting and usage
Base code and idea from a patch from by plinux at Taobao.

The idea is that we mark all memory that are thread specific with MY_THREAD_SPECIFIC.
Memory counting is done per thread in the my_malloc_size_cb_func callback function from my_malloc().
There are plenty of new asserts to ensure that for a debug server the counting is correct.

Information_schema.processlist gets two new columns: MEMORY_USED and EXAMINED_ROWS.
- The later is there mainly to show how query is progressing.

The following changes in interfaces was needed to get this to work:
- init_alloc_root() amd init_sql_alloc() has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- One now have to use alloc_root_set_min_malloc() to set min memory to be allocated by alloc_root()
- my_init_dynamic_array()  has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- my_net_init() has extra option so that one can mark memory with MY_THREAD_SPECIFIC
- Added flag for hash_init() so that one can mark hash table to be thread specific.
- Added flags to init_tree() so that one can mark tree to be thread specific.
- Removed with_delete option to init_tree(). Now one should instead use MY_TREE_WITH_DELETE_FLAG.
- Added flag to Warning_info::Warning_info() if the structure should be fully initialized.
- String elements can now be marked as thread specific.
- Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
- Changed type of myf from int to ulong, as this is always a set of bit flags.

Other things:
- Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
- We now also show EXAMINED_ROWS in SHOW PROCESSLIST
- Added new variable 'memory_used'
- Fixed bug where kill_threads_for_user() was using the wrong mem_root to allocate memory.
- Removed calls to the obsoleted function init_dynamic_array()
- Use set_current_thd() instead of my_pthread_setspecific_ptr(THR_THD,...)


client/completion_hash.cc:
  Updated call to init_alloc_root()
client/mysql.cc:
  Updated call to init_alloc_root()
client/mysqlbinlog.cc:
  init_dynamic_array() -> my_init_dynamic_array()
  Updated call to init_alloc_root()
client/mysqlcheck.c:
  Updated call to my_init_dynamic_array()
client/mysqldump.c:
  Updated call to init_alloc_root()
client/mysqltest.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Fixed compiler warnings
extra/comp_err.c:
  Updated call to my_init_dynamic_array()
extra/resolve_stack_dump.c:
  Updated call to my_init_dynamic_array()
include/hash.h:
  Added HASH_THREAD_SPECIFIC
include/heap.h:
  Added flag is internal temporary table.
include/my_dir.h:
  Safety fix: Ensure that MY_DONT_SORT and MY_WANT_STAT don't interfer with other mysys flags
include/my_global.h:
  Changed type of myf from int to ulong, as this is always a set of bit flags.
include/my_sys.h:
  Added MY_THREAD_SPECIFIC and MY_THREAD_MOVE
  Added malloc_flags to DYNAMIC_ARRAY
  Added extra mysys flag argument to my_init_dynamic_array()
  Removed deprecated functions init_dynamic_array() and my_init_dynamic_array.._ci
  Updated paramaters for init_alloc_root()
include/my_tree.h:
  Added my_flags to allow one to use MY_THREAD_SPECIFIC with hash tables.
  Removed with_delete. One should now instead use MY_TREE_WITH_DELETE_FLAG
  Updated parameters to init_tree()
include/myisamchk.h:
  Added malloc_flags to allow one to use MY_THREAD_SPECIFIC for checks.
include/mysql.h:
  Added MYSQL_THREAD_SPECIFIC_MALLOC
  Used 'unused1' to mark memory as thread specific.
include/mysql.h.pp:
  Updated file
include/mysql_com.h:
  Used 'unused1' to mark memory as thread specific.
  Updated parameters for my_net_init()
libmysql/libmysql.c:
  Updated call to init_alloc_root() to mark memory thread specific.
libmysqld/emb_qcache.cc:
  Updated call to init_alloc_root()
libmysqld/lib_sql.cc:
  Updated call to init_alloc_root()
mysql-test/r/create.result:
  Updated results
mysql-test/r/user_var.result:
  Updated results
mysql-test/suite/funcs_1/datadict/processlist_priv.inc:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/datadict/processlist_val.inc:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/r/is_columns_is.result:
  Update to handle new format of SHOW PROCESSLIST
mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result:
  Updated results
mysql-test/suite/funcs_1/r/processlist_val_no_prot.result:
  Updated results
mysql-test/t/show_explain.test:
  Fixed usage of debug variable so that one can run test with --debug
mysql-test/t/user_var.test:
  Added test of memory_usage variable.
mysys/array.c:
  Added extra my_flags option to init_dynamic_array() and init_dynamic_array2() so that one can mark memory with MY_THREAD_SPECIFIC
  All allocated memory is marked with the given my_flags.
  Removed obsolete function init_dynamic_array()
mysys/default.c:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
mysys/hash.c:
  Updated call to my_init_dynamic_array_ci().
  Allocated memory is marked with MY_THREAD_SPECIFIC if HASH_THREAD_SPECIFIC is used.
mysys/ma_dyncol.c:
  init_dynamic_array() -> my_init_dynamic_array()
  Added #if to get rid of compiler warnings
mysys/mf_tempdir.c:
  Updated call to my_init_dynamic_array()
mysys/my_alloc.c:
  Added extra parameter to init_alloc_root() so that one can mark memory with MY_THREAD_SPECIFIC
  Extend MEM_ROOT with a flag if memory is thread specific.
  This is stored in block_size, to keep the size of the MEM_ROOT object identical as before.
  Allocated memory is marked with MY_THREAD_SPECIFIC if used with init_alloc_root()
mysys/my_chmod.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_chsize.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_copy.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_create.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_delete.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_error.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_fopen.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_fstream.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_getwd.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_lib.c:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Updated DBUG_PRINT because of change of myf type
mysys/my_lock.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_malloc.c:
  Store at start of each allocated memory block the size of the block and if the block is thread specific.
  Call malloc_size_cb_func, if set, with the memory allocated/freed.
  Updated DBUG_PRINT because of change of myf type
mysys/my_open.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_pread.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_read.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_redel.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_rename.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_seek.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_sync.c:
  Updated DBUG_PRINT because of change of myf type
mysys/my_thr_init.c:
  Ensure that one can call my_thread_dbug_id() even if thread is not properly initialized.
mysys/my_write.c:
  Updated DBUG_PRINT because of change of myf type
mysys/mysys_priv.h:
  Updated parameters to sf_malloc and sf_realloc()
mysys/safemalloc.c:
  Added checking that for memory marked with MY_THREAD_SPECIFIC that it's the same thread that is allocation and freeing the memory.
  Added sf_malloc_dbug_id() to allow MariaDB to specify which THD is handling the memory.
  Added my_flags arguments to sf_malloc() and sf_realloc() to be able to mark memory with MY_THREAD_SPECIFIC.
  Added sf_report_leaked_memory() to get list of memory not freed by a thread.
mysys/tree.c:
  Added flags to init_tree() so that one can mark tree to be thread specific.
  Removed with_delete option to init_tree(). Now one should instead use MY_TREE_WITH_DELETE_FLAG.
  Updated call to init_alloc_root()
  All allocated memory is marked with the given malloc flags
mysys/waiting_threads.c:
  Updated call to my_init_dynamic_array()
sql-common/client.c:
  Updated call to init_alloc_root() and my_net_init() to mark memory thread specific.
  Updated call to my_init_dynamic_array().
  Added MYSQL_THREAD_SPECIFIC_MALLOC so that client can mark memory as MY_THREAD_SPECIFIC.
sql-common/client_plugin.c:
  Updated call to init_alloc_root()
sql/debug_sync.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/event_scheduler.cc:
  Removed calls to net_end() as this is now done in ~THD()
  Call set_current_thd() to ensure that memory is assigned to right thread.
sql/events.cc:
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/filesort.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/filesort_utils.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/ha_ndbcluster.cc:
  Updated call to init_alloc_root()
  Updated call to my_net_init()
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
sql/ha_ndbcluster_binlog.cc:
  Updated call to my_net_init()
  Updated call to init_sql_alloc()
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
sql/ha_partition.cc:
  Updated call to init_alloc_root()
sql/handler.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
  Added missing call to my_dir_end()
sql/item_func.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/item_subselect.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/item_sum.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/log.cc:
  More DBUG
  Updated call to init_alloc_root()
sql/mdl.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/mysqld.cc:
  Added total_memory_used
  Updated call to init_alloc_root()
  Move mysql_cond_broadcast() before my_thread_end()
  Added mariadb_dbug_id() to count memory per THD instead of per thread.
  Added my_malloc_size_cb_func() callback function for my_malloc() to count memory.
  Move initialization of mysqld_server_started and mysqld_server_initialized earlier.
  Updated call to my_init_dynamic_array().
  Updated call to my_net_init().
  Call my_pthread_setspecific_ptr(THR_THD,...) to ensure that memory is assigned to right thread.
  Added status variable 'memory_used'.
  Updated call to init_alloc_root()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/mysqld.h:
  Added set_current_thd()
sql/net_serv.cc:
  Added new parameter to my_net_init() so that one can mark memory with MY_THREAD_SPECIFIC.
  Store in net->thread_specific_malloc if memory is thread specific.
  Mark memory to be thread specific if requested.
sql/opt_range.cc:
  Updated call to my_init_dynamic_array()
  Updated call to init_sql_alloc()
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/opt_subselect.cc:
  Updated call to init_sql_alloc() to mark memory thread specific.
sql/protocol.cc:
  Fixed compiler warning
sql/records.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/rpl_filter.cc:
  Updated call to my_init_dynamic_array()
sql/rpl_handler.cc:
  Updated call to my_init_dynamic_array2()
sql/rpl_handler.h:
  Updated call to init_sql_alloc()
sql/rpl_mi.cc:
  Updated call to my_init_dynamic_array()
sql/rpl_tblmap.cc:
  Updated call to init_alloc_root()
sql/rpl_utility.cc:
  Updated call to my_init_dynamic_array()
sql/slave.cc:
  Initialize things properly before calling functions that allocate memory.
  Removed calls to net_end() as this is now done in ~THD()
sql/sp_head.cc:
  Updated call to init_sql_alloc()
  Updated call to my_init_dynamic_array()
  Added parameter to warning_info() that it should be fully initialized.
sql/sp_pcontext.cc:
  Updated call to my_init_dynamic_array()
sql/sql_acl.cc:
  Updated call to init_sql_alloc()
  Updated call to my_init_dynamic_array()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_admin.cc:
  Added parameter to warning_info() that it should be fully initialized.
sql/sql_analyse.h:
  Updated call to init_tree() to mark memory thread specific.
sql/sql_array.h:
  Updated call to my_init_dynamic_array() to mark memory thread specific.
sql/sql_audit.cc:
  Updated call to my_init_dynamic_array()
sql/sql_base.cc:
  Updated call to init_sql_alloc()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_cache.cc:
  Updated comment
sql/sql_class.cc:
  Added parameter to warning_info() that not initialize it until THD is fully created.
  Updated call to init_sql_alloc()
  Mark THD::user_vars has to be thread specific.
  Updated call to my_init_dynamic_array()
  Ensure that memory allocated by THD is assigned to the THD.
  More DBUG
  Always acll net_end() in ~THD()
  Assert that all memory signed to this THD is really deleted at ~THD.
  Fixed set_status_var_init() to not reset memory_used.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_class.h:
  Added MY_THREAD_SPECIFIC to allocated memory.
  Added malloc_size to THD to record allocated memory per THD.
sql/sql_delete.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_error.cc:
  Added 'initialize' parameter to Warning_info() to say if should allocate memory for it's structures.
  This is used by THD::THD() to not allocate memory until THD is ready.
  Added Warning_info::free_memory()
sql/sql_error.h:
  Updated Warning_info() class.
sql/sql_handler.cc:
  Updated call to init_alloc_root() to mark memory thread specific.
sql/sql_insert.cc:
  More DBUG
sql/sql_join_cache.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_lex.cc:
  Updated call to my_init_dynamic_array()
sql/sql_lex.h:
  Updated call to my_init_dynamic_array()
sql/sql_load.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/sql_parse.cc:
  Removed calls to net_end() and thd->cleanup() as these are now done in ~THD()
  Ensure that examined_row_count() is reset before query.
  Fixed bug where kill_threads_for_user() was using the wrong mem_root to allocate memory.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
  Don't restore thd->status_var.memory_used when restoring thd->status_var
sql/sql_plugin.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Don't allocate THD on the stack, as this causes problems with valgrind when doing thd memory counting.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_prepare.cc:
  Added parameter to warning_info() that it should be fully initialized.
  Updated call to init_sql_alloc() to mark memory thread specific.
sql/sql_reload.cc:
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_select.cc:
  Updated call to my_init_dynamic_array() and init_sql_alloc() to mark memory thread specific.
  Added MY_THREAD_SPECIFIC to allocated memory.
  More DBUG
sql/sql_servers.cc:
  Updated call to init_sql_alloc() to mark memory some memory thread specific.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_show.cc:
  Updated call to my_init_dynamic_array()
  Mark my_dir() memory thread specific.
  Use my_pthread_setspecific_ptr(THR_THD,...) to mark that allocated memory should be allocated to calling thread.
  More DBUG.
  Added malloc_size and examined_row_count to SHOW PROCESSLIST.
  Added MY_THREAD_SPECIFIC to allocated memory.
  Updated call to init_sql_alloc()
  Added parameter to warning_info() that it should be fully initialized.
sql/sql_statistics.cc:
  Fixed compiler warning
sql/sql_string.cc:
  String elements can now be marked as thread specific.
sql/sql_string.h:
  String elements can now be marked as thread specific.
sql/sql_table.cc:
  Updated call to init_sql_alloc() and my_malloc() to mark memory thread specific
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
  Fixed compiler warning
sql/sql_test.cc:
  Updated call to my_init_dynamic_array() to mark memory thread specific.
sql/sql_trigger.cc:
  Updated call to init_sql_alloc()
sql/sql_udf.cc:
  Updated call to init_sql_alloc()
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/sql_update.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
sql/table.cc:
  Updated call to init_sql_alloc().
  Mark memory used by temporary tables, that are not for slave threads, as MY_THREAD_SPECIFIC
  Updated call to init_sql_alloc()
sql/thr_malloc.cc:
  Added my_flags argument to init_sql_alloc() to be able to mark memory as MY_THREAD_SPECIFIC.
sql/thr_malloc.h:
  Updated prototype for init_sql_alloc()
sql/tztime.cc:
  Updated call to init_sql_alloc()
  Updated call to init_alloc_root() to mark memory thread specific.
  my_pthread_setspecific_ptr(THR_THD,...) -> set_current_thd()
sql/uniques.cc:
  Updated calls to init_tree(), my_init_dynamic_array() and my_malloc() to mark memory thread specific.
sql/unireg.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
storage/csv/ha_tina.cc:
  Updated call to init_alloc_root()
storage/federated/ha_federated.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
  Ensure that memory allocated by fedarated is registered for the system, not for the thread.
storage/federatedx/federatedx_io_mysql.cc:
  Updated call to my_init_dynamic_array()
storage/federatedx/ha_federatedx.cc:
  Updated call to init_alloc_root()
  Updated call to my_init_dynamic_array()
storage/heap/ha_heap.cc:
  Added MY_THREAD_SPECIFIC to allocated memory.
storage/heap/heapdef.h:
  Added parameter to hp_get_new_block() to be able to do thread specific memory tagging.
storage/heap/hp_block.c:
  Added parameter to hp_get_new_block() to be able to do thread specific memory tagging.
storage/heap/hp_create.c:
  - Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
  - Use MY_TREE_WITH_DELETE instead of removed option 'with_delete'.
storage/heap/hp_open.c:
  Internal HEAP tables are now marking it's memory as MY_THREAD_SPECIFIC.
storage/heap/hp_write.c:
  Added new parameter to hp_get_new_block()
storage/maria/ma_bitmap.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_blockrec.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_check.c:
  Updated call to init_alloc_root()
storage/maria/ma_ft_boolean_search.c:
  Updated calls to init_tree() and init_alloc_root()
storage/maria/ma_ft_nlq_search.c:
  Updated call to init_tree()
storage/maria/ma_ft_parser.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/maria/ma_loghandler.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_open.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_sort.c:
  Updated call to my_init_dynamic_array()
storage/maria/ma_write.c:
  Updated calls to my_init_dynamic_array() and init_tree()
storage/maria/maria_pack.c:
  Updated call to init_tree()
storage/maria/unittest/sequence_storage.c:
  Updated call to my_init_dynamic_array()
storage/myisam/ft_boolean_search.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/myisam/ft_nlq_search.c:
  Updated call to init_tree()
storage/myisam/ft_parser.c:
  Updated call to init_tree()
  Updated call to init_alloc_root()
storage/myisam/ft_stopwords.c:
  Updated call to init_tree()
storage/myisam/mi_check.c:
  Updated call to init_alloc_root()
storage/myisam/mi_write.c:
  Updated call to my_init_dynamic_array()
  Updated call to init_tree()
storage/myisam/myisamlog.c:
  Updated call to init_tree()
storage/myisam/myisampack.c:
  Updated call to init_tree()
storage/myisam/sort.c:
  Updated call to my_init_dynamic_array()
storage/myisammrg/ha_myisammrg.cc:
  Updated call to init_sql_alloc()
storage/perfschema/pfs_check.cc:
  Rest current_thd
storage/perfschema/pfs_instr.cc:
  Removed DBUG_ENTER/DBUG_VOID_RETURN as at this point my_thread_var is not allocated anymore, which can cause problems.
support-files/compiler_warnings.supp:
  Disable compiler warning from offsetof macro.
2013-01-23 16:16:14 +01:00

849 lines
22 KiB
C

/*
Copyright (c) 2000, 2010, 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 as published by
the Free Software Foundation; version 2 of the License.
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 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 */
/* write whats in isam.log */
#ifndef USE_MY_FUNC
#define USE_MY_FUNC
#endif
#include "myisamdef.h"
#include <my_tree.h>
#include <stdarg.h>
#ifdef HAVE_GETRUSAGE
#include <sys/resource.h>
#endif
#define FILENAME(A) (A ? A->show_name : "Unknown")
struct file_info {
long process;
int filenr,id;
uint rnd;
char *name, *show_name;
uchar *record;
MI_INFO *isam;
my_bool closed, used;
ulong accessed;
};
struct test_if_open_param {
char * name;
int max_id;
};
struct st_access_param
{
ulong min_accessed;
struct file_info *found;
};
#define NO_FILEPOS (ulong) ~0L
extern int main(int argc,char * *argv);
static void get_options(int *argc,char ***argv);
static int examine_log(char * file_name,char **table_names);
static int read_string(IO_CACHE *file,uchar* *to,uint length);
static int file_info_compare(void *cmp_arg, void *a,void *b);
static int test_if_open(struct file_info *key,element_count count,
struct test_if_open_param *param);
static void fix_blob_pointers(MI_INFO *isam,uchar *record);
static int test_when_accessed(struct file_info *key,element_count count,
struct st_access_param *access_param);
static void file_info_free(struct file_info *info);
static int close_some_file(TREE *tree);
static int reopen_closed_file(TREE *tree,struct file_info *file_info);
static int find_record_with_key(struct file_info *file_info,uchar *record);
static void printf_log(const char *str,...);
static my_bool cmp_filename(struct file_info *file_info,char * name);
static uint verbose=0,update=0,test_info=0,max_files=0,re_open_count=0,
recover=0,prefix_remove=0,opt_processes=0;
static char *log_filename=0, *filepath=0, *write_filename=0;
static char *record_pos_file= 0;
static ulong com_count[10][3],number_of_commands=(ulong) ~0L,
isamlog_process;
static my_off_t isamlog_filepos,start_offset=0,record_pos= HA_OFFSET_ERROR;
static const char *command_name[]=
{"open","write","update","delete","close","extra","lock","re-open",
"delete-all", NullS};
int main(int argc, char **argv)
{
int error,i,first;
ulong total_count,total_error,total_recover;
MY_INIT(argv[0]);
log_filename=myisam_log_filename;
get_options(&argc,&argv);
/* Number of MyISAM files we can have open at one time */
max_files= (my_set_max_open_files(min(max_files,8))-6)/2;
if (update)
printf("Trying to %s MyISAM files according to log '%s'\n",
(recover ? "recover" : "update"),log_filename);
error= examine_log(log_filename,argv);
if (update && ! error)
puts("Tables updated successfully");
total_count=total_error=total_recover=0;
for (i=first=0 ; command_name[i] ; i++)
{
if (com_count[i][0])
{
if (!first++)
{
if (verbose || update)
puts("");
puts("Commands Used count Errors Recover errors");
}
printf("%-12s%9ld%10ld%17ld\n",command_name[i],com_count[i][0],
com_count[i][1],com_count[i][2]);
total_count+=com_count[i][0];
total_error+=com_count[i][1];
total_recover+=com_count[i][2];
}
}
if (total_count)
printf("%-12s%9ld%10ld%17ld\n","Total",total_count,total_error,
total_recover);
if (re_open_count)
printf("Had to do %d re-open because of too few possibly open files\n",
re_open_count);
(void) mi_panic(HA_PANIC_CLOSE);
my_free_open_file_info();
my_end(test_info ? MY_CHECK_ERROR | MY_GIVE_INFO : MY_CHECK_ERROR);
exit(error);
return 0; /* No compiler warning */
} /* main */
static void get_options(register int *argc, register char ***argv)
{
int help,version;
const char *pos,*usage;
char option;
help=0;
usage="Usage: %s [-?iruvDIV] [-c #] [-f #] [-F filepath/] [-o #] [-R file recordpos] [-w write_file] [log-filename [table ...]] \n";
pos="";
while (--*argc > 0 && *(pos = *(++*argv)) == '-' ) {
while (*++pos)
{
version=0;
switch((option=*pos)) {
case '#':
DBUG_PUSH (++pos);
pos=" "; /* Skip rest of arg */
break;
case 'c':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
number_of_commands=(ulong) atol(pos);
pos=" ";
break;
case 'u':
update=1;
break;
case 'f':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
max_files=(uint) atoi(pos);
pos=" ";
break;
case 'i':
test_info=1;
break;
case 'o':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
start_offset=(my_off_t) strtoll(pos,NULL,10);
pos=" ";
break;
case 'p':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
prefix_remove=atoi(pos);
break;
case 'r':
update=1;
recover++;
break;
case 'P':
opt_processes=1;
break;
case 'R':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
record_pos_file=(char*) pos;
if (!--*argc)
goto err;
record_pos=(my_off_t) strtoll(*(++*argv),NULL,10);
pos=" ";
break;
case 'v':
verbose++;
break;
case 'w':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
write_filename=(char*) pos;
pos=" ";
break;
case 'F':
if (! *++pos)
{
if (!--*argc)
goto err;
else
pos= *(++*argv);
}
filepath= (char*) pos;
pos=" ";
break;
case 'V':
version=1;
/* Fall through */
case 'I':
case '?':
printf("%s Ver 1.4 for %s at %s\n",my_progname,SYSTEM_TYPE,
MACHINE_TYPE);
puts("By Monty, for your professional use\n");
if (version)
break;
puts("Write info about whats in a MyISAM log file.");
printf("If no file name is given %s is used\n",log_filename);
puts("");
printf(usage,my_progname);
puts("");
puts("Options: -? or -I \"Info\" -V \"version\" -c \"do only # commands\"");
puts(" -f \"max open files\" -F \"filepath\" -i \"extra info\"");
puts(" -o \"offset\" -p # \"remove # components from path\"");
puts(" -r \"recover\" -R \"file recordposition\"");
puts(" -u \"update\" -v \"verbose\" -w \"write file\"");
puts(" -D \"myisam compiled with DBUG\" -P \"processes\"");
puts("\nOne can give a second and a third '-v' for more verbose.");
puts("Normaly one does a update (-u).");
puts("If a recover is done all writes and all possibly updates and deletes is done\nand errors are only counted.");
puts("If one gives table names as arguments only these tables will be updated\n");
help=1;
break;
default:
printf("illegal option: \"-%c\"\n",*pos);
break;
}
}
}
if (! *argc)
{
if (help)
exit(0);
(*argv)++;
}
if (*argc >= 1)
{
log_filename=(char*) pos;
(*argc)--;
(*argv)++;
}
return;
err:
(void) fprintf(stderr,"option \"%c\" used without or with wrong argument\n",
option);
exit(1);
}
static int examine_log(char * file_name, char **table_names)
{
uint command,result,files_open;
ulong access_time,length;
my_off_t filepos;
int lock_command,mi_result;
char isam_file_name[FN_REFLEN],llbuff[21],llbuff2[21];
uchar head[20];
uchar* buff;
struct test_if_open_param open_param;
IO_CACHE cache;
File file;
FILE *write_file;
enum ha_extra_function extra_command;
TREE tree;
struct file_info file_info,*curr_file_info;
DBUG_ENTER("examine_log");
if ((file=my_open(file_name,O_RDONLY,MYF(MY_WME))) < 0)
DBUG_RETURN(1);
write_file=0;
if (write_filename)
{
if (!(write_file=my_fopen(write_filename,O_WRONLY,MYF(MY_WME))))
{
my_close(file,MYF(0));
DBUG_RETURN(1);
}
}
init_io_cache(&cache,file,0,READ_CACHE,start_offset,0,MYF(0));
bzero((uchar*) com_count,sizeof(com_count));
init_tree(&tree,0,0,sizeof(file_info),(qsort_cmp2) file_info_compare,
(tree_element_free) file_info_free, NULL,
MYF(MY_TREE_WITH_DELETE));
(void) init_key_cache(dflt_key_cache,KEY_CACHE_BLOCK_SIZE,KEY_CACHE_SIZE,
0, 0, 0);
files_open=0; access_time=0;
while (access_time++ != number_of_commands &&
!my_b_read(&cache,(uchar*) head,9))
{
isamlog_filepos=my_b_tell(&cache)-9L;
file_info.filenr= mi_uint2korr(head+1);
isamlog_process=file_info.process=(long) mi_uint4korr(head+3);
if (!opt_processes)
file_info.process=0;
result= mi_uint2korr(head+7);
if ((curr_file_info=(struct file_info*) tree_search(&tree, &file_info,
tree.custom_arg)))
{
curr_file_info->accessed=access_time;
if (update && curr_file_info->used && curr_file_info->closed)
{
if (reopen_closed_file(&tree,curr_file_info))
{
command=sizeof(com_count)/sizeof(com_count[0][0])/3;
result=0;
goto com_err;
}
}
}
command=(uint) head[0];
if (command < sizeof(com_count)/sizeof(com_count[0][0])/3 &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
{
com_count[command][0]++;
if (result)
com_count[command][1]++;
}
switch ((enum myisam_log_commands) command) {
case MI_LOG_OPEN:
if (!table_names[0])
{
com_count[command][0]--; /* Must be counted explicite */
if (result)
com_count[command][1]--;
}
if (curr_file_info)
printf("\nWarning: %s is opened with same process and filenumber\n"
"Maybe you should use the -P option ?\n",
curr_file_info->show_name);
if (my_b_read(&cache,(uchar*) head,2))
goto err;
buff= 0;
file_info.name=0;
file_info.show_name=0;
file_info.record=0;
if (read_string(&cache, &buff, (uint) mi_uint2korr(head)))
goto err;
{
uint i;
char *pos,*to;
/* Fix if old DOS files to new format */
for (pos=file_info.name=(char*)buff; (pos=strchr(pos,'\\')) ; pos++)
*pos= '/';
pos=file_info.name;
for (i=0 ; i < prefix_remove ; i++)
{
char *next;
if (!(next=strchr(pos,'/')))
break;
pos=next+1;
}
to=isam_file_name;
if (filepath)
to=convert_dirname(isam_file_name,filepath,NullS);
strmov(to,pos);
fn_ext(isam_file_name)[0]=0; /* Remove extension */
}
open_param.name=file_info.name;
open_param.max_id=0;
(void) tree_walk(&tree,(tree_walk_action) test_if_open,(void*) &open_param,
left_root_right);
file_info.id=open_param.max_id+1;
/*
* In the line below +10 is added to accomodate '<' and '>' chars
* plus '\0' at the end, so that there is place for 7 digits.
* It is improbable that same table can have that many entries in
* the table cache.
* The additional space is needed for the sprintf commands two lines
* below.
*/
file_info.show_name=my_memdup(isam_file_name,
(uint) strlen(isam_file_name)+10,
MYF(MY_WME));
if (file_info.id > 1)
sprintf(strend(file_info.show_name),"<%d>",file_info.id);
file_info.closed=1;
file_info.accessed=access_time;
file_info.used=1;
if (table_names[0])
{
char **name;
file_info.used=0;
for (name=table_names ; *name ; name++)
{
if (!strcmp(*name,isam_file_name))
file_info.used=1; /* Update/log only this */
}
}
if (update && file_info.used)
{
if (files_open >= max_files)
{
if (close_some_file(&tree))
goto com_err;
files_open--;
}
if (!(file_info.isam= mi_open(isam_file_name,O_RDWR,
HA_OPEN_WAIT_IF_LOCKED)))
goto com_err;
if (!(file_info.record=my_malloc(file_info.isam->s->base.reclength,
MYF(MY_WME))))
goto end;
files_open++;
file_info.closed=0;
}
(void) tree_insert(&tree, (uchar*) &file_info, 0, tree.custom_arg);
if (file_info.used)
{
if (verbose && !record_pos_file)
printf_log("%s: open -> %d",file_info.show_name, file_info.filenr);
com_count[command][0]++;
if (result)
com_count[command][1]++;
}
break;
case MI_LOG_CLOSE:
if (verbose && !record_pos_file &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
printf_log("%s: %s -> %d",FILENAME(curr_file_info),
command_name[command],result);
if (curr_file_info)
{
if (!curr_file_info->closed)
files_open--;
(void) tree_delete(&tree, (uchar*) curr_file_info, 0, tree.custom_arg);
}
break;
case MI_LOG_EXTRA:
if (my_b_read(&cache,(uchar*) head,1))
goto err;
extra_command=(enum ha_extra_function) head[0];
if (verbose && !record_pos_file &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
printf_log("%s: %s(%d) -> %d",FILENAME(curr_file_info),
command_name[command], (int) extra_command,result);
if (update && curr_file_info && !curr_file_info->closed)
{
if (mi_extra(curr_file_info->isam, extra_command, 0) != (int) result)
{
fflush(stdout);
(void) fprintf(stderr,
"Warning: error %d, expected %d on command %s at %s\n",
my_errno,result,command_name[command],
llstr(isamlog_filepos,llbuff));
fflush(stderr);
}
}
break;
case MI_LOG_DELETE:
if (my_b_read(&cache,(uchar*) head,8))
goto err;
filepos=mi_sizekorr(head);
if (verbose && (!record_pos_file ||
((record_pos == filepos || record_pos == NO_FILEPOS) &&
!cmp_filename(curr_file_info,record_pos_file))) &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
printf_log("%s: %s at %ld -> %d",FILENAME(curr_file_info),
command_name[command],(long) filepos,result);
if (update && curr_file_info && !curr_file_info->closed)
{
if (mi_rrnd(curr_file_info->isam,curr_file_info->record,filepos))
{
if (!recover)
goto com_err;
if (verbose)
printf_log("error: Didn't find row to delete with mi_rrnd");
com_count[command][2]++; /* Mark error */
}
mi_result=mi_delete(curr_file_info->isam,curr_file_info->record);
if ((mi_result == 0 && result) ||
(mi_result && (uint) my_errno != result))
{
if (!recover)
goto com_err;
if (mi_result)
com_count[command][2]++; /* Mark error */
if (verbose)
printf_log("error: Got result %d from mi_delete instead of %d",
mi_result, result);
}
}
break;
case MI_LOG_WRITE:
case MI_LOG_UPDATE:
if (my_b_read(&cache,(uchar*) head,12))
goto err;
filepos=mi_sizekorr(head);
length=mi_uint4korr(head+8);
buff=0;
if (read_string(&cache,&buff,(uint) length))
goto err;
if ((!record_pos_file ||
((record_pos == filepos || record_pos == NO_FILEPOS) &&
!cmp_filename(curr_file_info,record_pos_file))) &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
{
if (write_file &&
(my_fwrite(write_file,buff,length,MYF(MY_WAIT_IF_FULL | MY_NABP))))
goto end;
if (verbose)
printf_log("%s: %s at %ld, length=%ld -> %d",
FILENAME(curr_file_info),
command_name[command], filepos,length,result);
}
if (update && curr_file_info && !curr_file_info->closed)
{
if (curr_file_info->isam->s->base.blobs)
fix_blob_pointers(curr_file_info->isam,buff);
if ((enum myisam_log_commands) command == MI_LOG_UPDATE)
{
if (mi_rrnd(curr_file_info->isam,curr_file_info->record,filepos))
{
if (!recover)
{
result=0;
goto com_err;
}
if (verbose)
printf_log("error: Didn't find row to update with mi_rrnd");
if (recover == 1 || result ||
find_record_with_key(curr_file_info,buff))
{
com_count[command][2]++; /* Mark error */
break;
}
}
mi_result=mi_update(curr_file_info->isam,curr_file_info->record,
buff);
if ((mi_result == 0 && result) ||
(mi_result && (uint) my_errno != result))
{
if (!recover)
goto com_err;
if (verbose)
printf_log("error: Got result %d from mi_update instead of %d",
mi_result, result);
if (mi_result)
com_count[command][2]++; /* Mark error */
}
}
else
{
mi_result=mi_write(curr_file_info->isam,buff);
if ((mi_result == 0 && result) ||
(mi_result && (uint) my_errno != result))
{
if (!recover)
goto com_err;
if (verbose)
printf_log("error: Got result %d from mi_write instead of %d",
mi_result, result);
if (mi_result)
com_count[command][2]++; /* Mark error */
}
if (!recover && filepos != curr_file_info->isam->lastpos)
{
printf("error: Wrote at position: %s, should have been %s",
llstr(curr_file_info->isam->lastpos,llbuff),
llstr(filepos,llbuff2));
goto end;
}
}
}
my_free(buff);
break;
case MI_LOG_LOCK:
if (my_b_read(&cache,(uchar*) head,sizeof(lock_command)))
goto err;
memcpy(&lock_command, head, sizeof(lock_command));
if (verbose && !record_pos_file &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
printf_log("%s: %s(%d) -> %d\n",FILENAME(curr_file_info),
command_name[command],lock_command,result);
if (update && curr_file_info && !curr_file_info->closed)
{
if (mi_lock_database(curr_file_info->isam,lock_command) !=
(int) result)
goto com_err;
}
break;
case MI_LOG_DELETE_ALL:
if (verbose && !record_pos_file &&
(!table_names[0] || (curr_file_info && curr_file_info->used)))
printf_log("%s: %s -> %d\n",FILENAME(curr_file_info),
command_name[command],result);
break;
default:
fflush(stdout);
(void) fprintf(stderr,
"Error: found unknown command %d in logfile, aborted\n",
command);
fflush(stderr);
goto end;
}
}
end_key_cache(dflt_key_cache,1);
delete_tree(&tree);
(void) end_io_cache(&cache);
(void) my_close(file,MYF(0));
if (write_file && my_fclose(write_file,MYF(MY_WME)))
DBUG_RETURN(1);
DBUG_RETURN(0);
err:
fflush(stdout);
(void) fprintf(stderr,"Got error %d when reading from logfile\n",my_errno);
fflush(stderr);
goto end;
com_err:
fflush(stdout);
(void) fprintf(stderr,"Got error %d, expected %d on command %s at %s\n",
my_errno,result,command_name[command],
llstr(isamlog_filepos,llbuff));
fflush(stderr);
end:
end_key_cache(dflt_key_cache, 1);
delete_tree(&tree);
(void) end_io_cache(&cache);
(void) my_close(file,MYF(0));
if (write_file)
(void) my_fclose(write_file,MYF(MY_WME));
DBUG_RETURN(1);
}
static int read_string(IO_CACHE *file, register uchar* *to, register uint length)
{
DBUG_ENTER("read_string");
if (*to)
my_free(*to);
if (!(*to= (uchar*) my_malloc(length+1,MYF(MY_WME))) ||
my_b_read(file,(uchar*) *to,length))
{
if (*to)
my_free(*to);
*to= 0;
DBUG_RETURN(1);
}
*((uchar*) *to+length)= '\0';
DBUG_RETURN (0);
} /* read_string */
static int file_info_compare(void* cmp_arg __attribute__((unused)),
void *a, void *b)
{
long lint;
if ((lint=((struct file_info*) a)->process -
((struct file_info*) b)->process))
return lint < 0L ? -1 : 1;
return ((struct file_info*) a)->filenr - ((struct file_info*) b)->filenr;
}
/* ARGSUSED */
static int test_if_open (struct file_info *key,
element_count count __attribute__((unused)),
struct test_if_open_param *param)
{
if (!strcmp(key->name,param->name) && key->id > param->max_id)
param->max_id=key->id;
return 0;
}
static void fix_blob_pointers(MI_INFO *info, uchar *record)
{
uchar *pos;
MI_BLOB *blob,*end;
pos=record+info->s->base.reclength;
for (end=info->blobs+info->s->base.blobs, blob= info->blobs;
blob != end ;
blob++)
{
memcpy(record+blob->offset+blob->pack_length, &pos, sizeof(char*));
pos+=_mi_calc_blob_length(blob->pack_length,record+blob->offset);
}
}
/* close the file with hasn't been accessed for the longest time */
/* ARGSUSED */
static int test_when_accessed (struct file_info *key,
element_count count __attribute__((unused)),
struct st_access_param *access_param)
{
if (key->accessed < access_param->min_accessed && ! key->closed)
{
access_param->min_accessed=key->accessed;
access_param->found=key;
}
return 0;
}
static void file_info_free(struct file_info *fileinfo)
{
DBUG_ENTER("file_info_free");
if (update)
{
if (!fileinfo->closed)
(void) mi_close(fileinfo->isam);
if (fileinfo->record)
my_free(fileinfo->record);
}
my_free(fileinfo->name);
my_free(fileinfo->show_name);
DBUG_VOID_RETURN;
}
static int close_some_file(TREE *tree)
{
struct st_access_param access_param;
access_param.min_accessed=LONG_MAX;
access_param.found=0;
(void) tree_walk(tree,(tree_walk_action) test_when_accessed,
(void*) &access_param,left_root_right);
if (!access_param.found)
return 1; /* No open file that is possibly to close */
if (mi_close(access_param.found->isam))
return 1;
access_param.found->closed=1;
return 0;
}
static int reopen_closed_file(TREE *tree, struct file_info *fileinfo)
{
char name[FN_REFLEN];
if (close_some_file(tree))
return 1; /* No file to close */
strmov(name,fileinfo->show_name);
if (fileinfo->id > 1)
*strrchr(name,'<')='\0'; /* Remove "<id>" */
if (!(fileinfo->isam= mi_open(name,O_RDWR,HA_OPEN_WAIT_IF_LOCKED)))
return 1;
fileinfo->closed=0;
re_open_count++;
return 0;
}
/* Try to find record with uniq key */
static int find_record_with_key(struct file_info *file_info, uchar *record)
{
uint key;
MI_INFO *info=file_info->isam;
uchar tmp_key[HA_MAX_KEY_BUFF];
for (key=0 ; key < info->s->base.keys ; key++)
{
if (mi_is_key_active(info->s->state.key_map, key) &&
info->s->keyinfo[key].flag & HA_NOSAME)
{
(void) _mi_make_key(info,key,tmp_key,record,0L);
return mi_rkey(info,file_info->record,(int) key,tmp_key,0,
HA_READ_KEY_EXACT);
}
}
return 1;
}
static void printf_log(const char *format,...)
{
char llbuff[21];
va_list args;
va_start(args,format);
if (verbose > 2)
printf("%9s:",llstr(isamlog_filepos,llbuff));
if (verbose > 1)
printf("%5ld ",isamlog_process); /* Write process number */
(void) vprintf((char*) format,args);
putchar('\n');
va_end(args);
}
static my_bool cmp_filename(struct file_info *file_info, char * name)
{
if (!file_info)
return 1;
return strcmp(file_info->name,name) ? 1 : 0;
}
#include "mi_extrafunc.h"