Source for 8.4.2.0 version of extsql.c
/* Copyright (C) 2006, 2007, 2008, 2009, 2010 Software Workshop Incorporated
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; either version 2 of the License, or
(at your option) any later version.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#define PGSQL 1
#ifdef PGSQL
#include "utils/extsql.h"
#else // MYSQL
#include "mysql_priv.h"
#include "stacktrace.h"
#endif // PGSQL or MYSQL
// DEBUG FLAGS -- we show decimal, has to be used in SET GLOBAL extsql_debug= val
#define STATS_DUMP 1 // dump stats internal data on every SHOW command
#define STATS_DUMP_ONCE 2 // just dump it once and clear stats_debug ZZ
#define STATS_SHOW_PARAMS 4 // show parameters to SHOW STATISTICS
#define STATS_INC_PARAMS 8 // print warning on bad param to extsql_inc
#define STATS_ADMIN 16 // allow write/read/reset commands
#define STATS_TEST_TWO 32 // testing
#define STATS_INC_VAR 64 // show details on stats increment
#define STATS_THD_INIT_PARAMS 128 // show parameters on thread init
#define STATS_THD_INIT_LOOP 256 // show search for matching/new instance of class
#define STATS_DISABLE_INC 512 // disable extsql_inc processing
#define STATS_THREAD_CHK 1024 // do a check of the thread on each inc, only print errors
#define STATS_DUMP_START 2048 // dump all tracking info at server start
#define STATS_TIME_DETAIL 4096 // show start secs/minutes if min/hour are units
#define STATS_EXTRA_VARS 8192 // allow user to select untested vars for tracking
#define STATS_MEM_ALLOC 16384 // show startup mem allocs
#define STATS_TIME_CHANGE 32768 // show start of time roll over processing
#define STATS_RELOAD 65536 // show reload processing
#define STATS_TEST_ONE 131072 // testing
#define STATS_PID 262144 // show server pid as part of show statistics, for gdb
#define PROG_POINT(x,y) sql_print_information("ExtSQL: PROGRESS POINT %s (0x%x)", x, y)
// LIMITS
#define STATS_MAX_TIME_LABEL 20
#define STATS_WARNING_LIMIT 10
#define MAX_BUFF_SIZE 2048 // used for temporary buffs of user input (product of next two)
#define MAX_VAR_SIZE 45 // max length we allows for a Var name that is tracked.
#define MAX_VARS_IN_CLASS 50 // max number of vars in single class def & row of output
#define DATE_SIZE 30
// MISC
#define ARRAY_INDEX(i,j,size_j,k,size_k) ( (i * size_j +j) * size_k + k)
// just testing
time_t StartTime;
// GLOBAL's defined in GLOBAL struct, extsql.h,
// few vars here where compiler needs static addresses. Set global in extsql_init_globals
// these are globals initially set in postgresql.conf -- extsql_init has not been called yet,
// need to have a static address allocated, extsql_init_globals will then copy to Shared.
ulong extsql_active=0, extsql_counter=0, extsql_debug=0, queries=0, connects=0;
char *extsql_class_list=NULL, *extsql_reload_file=NULL;
#ifdef PGSQL
pGLOBAL Shared; // Used for shared memory/globals - only dynamic allocation at start.
#else // MYSQL
GLOBAL GShared; // can be static, shared between threads
pGLOBAL Shared = &GShared;
#endif
// CONSTANT GLOBALS HERE
char *extsqlClassesSupported[STATS_MAX_CLASSES] = {"condb", "conuser", "db", "host", "server", "user", NULL};
char *extsql_key, *extsql_users;
static char *extsql_version = "\nExtSQL version: __STATS_VERSION__\n"; // keep \n at start and end!
// vars we have tested and confirmed with, they can set STATS_EXTRA_VARS to override.
static char *allowedVarsArray[MAX_VARS] = {"Com_begin", "Com_commit", "Com_create_table",
"Com_delete", "Com_drop_table", "Com_insert", "Com_lock_tables", "Com_replace",
"Com_rollback", "Com_select", "Com_set_option", "Com_show_fields", "Com_stmt_execute",
"Com_stmt_prepare", "Com_update", "Connections", "Created_tmp_tables",
"Created_tmp_disk_tables", "Handler_read_rnd", "Handler_read_rnd_next",
"Qcache_hits", "Questions", "Select_full_join", "Select_range_check",
"Select_scan", "Slow_queries", NULL };
static char *startTypeName[] = { "FAILURE", "NORMAL", "RESET", "RELOAD" };
char **allowedVars = allowedVarsArray;
static char *adminDisabled = "ExtSQL: In this release STATISTICS RESET/READ/WRITE are disabled by default due \
to incomplete testing. If you wish to use these commands you must added the following to the server \
config file and restart the server: extsql_debug = 16";
// FUNCTION prototypes
int extsql_init_globals(void);
static void extsql_stats_dump(void);
static void extsql_thread_dump(THD *thd, int doDump);
static int extsql_store_var_addr(char *varAddr);
static void extsql_init_proc_vars(int *threadID, int *command, char **user, char **db, char **host,
char **proc_info, int **indexPtr, int **statVerify);
#ifdef PGSQL
ulong Com_begin, Com_commit, Com_create_table, Com_delete,
Com_drop_table, Com_insert, Com_rollback, Com_select, Com_update, Connections, Questions;
char *ShmemPtr = NULL; // for shared memory
static slock_t *MemLock = NULL; // spin lock needs to be in shared mem region.
char **NextFree = NULL; // for shared memory!
struct show_var_st status_vars[] = {
{"Com_begin", &Com_begin},
{"Com_commit", &Com_commit},
{"Com_create_table", &Com_create_table},
{"Com_delete", &Com_delete},
{"Com_drop_table", &Com_drop_table},
{"Com_insert", &Com_insert},
{"Com_rollback", &Com_rollback},
{"Com_select", &Com_select},
{"Com_update", &Com_update},
{"Connections", &Connections},
{"Questions", &Questions},
{NullS, (ulong *) 0}
};
char *command_name[] = {"n/a for pgsql", "n/a for pgsql", NULL};
// change in number of args from 7.4.19 -> 8.4
#ifdef PGSQL_8
#define TupleDescInitEntry(a,b,c,d,e,f,g) TupleDescInitEntry(a,b,c,d,e,f)
#endif
int mysql_show_extsql(THD *thd, const char *stats_class, DestReceiver *dest, List *stats_vars,
uint stats_hourly, const char *wild, char *stats_order_var,
const uint stats_order_dir, const uint stats_order_limit, char *stats_where_var,
const uint stats_where_num, const uint stats_where_char);
#define SHMEM_SIZE 1000000 // ZZ - need to predict based on class list for pgsql
#define SHMEM_RESERVE 100000
// need our own my_malloc to zero alloc'd memory
// NOTE - this allocs from our limited shared memory area, we don't FREE. This should
// only be used for extsql data
// Temporary buffers used for i/o should be alloced with just "malloc/free"
char * extsql_malloc(int, uint);
char * extsql_malloc(int size, uint flags) { // from our shared memory segment, pointed at by ShmemPtr;
char *newStart; // similiar code in shmem.c
char *newFree;
char *addr;
if (ShmemPtr == NULL || NextFree == NULL) { // not allocated yet
sql_print_error("ExtSQL extsql_malloc failure, shared memory not allocated!");
return(NULL);
}
size = MAXALIGN(size);
SpinLockAcquire(MemLock);
newStart = *NextFree;
/* extra alignment for large requests, since they are probably buffers */
if (size >= BLCKSZ) {
newStart = (char *) BUFFERALIGN(newStart);
}
newFree = newStart + size;
if (newFree <= SHMEM_SIZE + ShmemPtr) {
addr = newStart;
*NextFree = newFree;
} else {
addr = NULL;
}
SpinLockRelease(MemLock);
if (!addr)
ereport(WARNING,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("ExtSQL - out of shared memory")));
bzero(addr, size);
if STATS_DEBUG(STATS_MEM_ALLOC ) {
sql_print_information("ExtSQL: alloc at 0x%x, of %d bytes",
(uint)addr, size);
}
return(addr);
} // end my_malloc for PGSQL
// need our own strdup for pgsql
// NOTE - this allocs from our limited shared memory area, we don't FREE. This should
// only be used for extsql data
// Temporary buffers used for i/o should be alloced with just "strdup"
char * extsql_strdup(char *, uint flags);
char * extsql_strdup(char *srcStr, uint flags) {
int size = strlen(srcStr);
char *newStr = NULL;
newStr = extsql_malloc(size+1, 0); // second param doesn't matter for PGSQL
if (!newStr) {
sql_print_error("ExtSQL my_strdup: failed to allocate size (%d)", size);
return(NULL);
} else {
strcpy(newStr, srcStr);
}
return(newStr);
} // end my_strdup for PGSQL
static void my_datetime_to_str(time_t *, char *);
static void my_datetime_to_str(time_t *secsNow, char *date) {
struct tm *timeNow;
timeNow=localtime(secsNow);
strftime(date, DATE_SIZE , "%m/%d/%y %H:%M:%S", timeNow);
}
// wild_case_compare -- from MySQL source, not ours. We don't use
// charset info for PGSQL.
int wild_case_compare(int cs, const char *str,const char *wildstr);
int wild_case_compare(int cs, const char *str,const char *wildstr)
{
char wild_many='%', wild_one='_', wild_prefix='\\';
int flag;
while (*wildstr) {
while (*wildstr && *wildstr != wild_many && *wildstr != wild_one) {
if (*wildstr == wild_prefix && wildstr[1])
wildstr++;
if (toupper(*wildstr++) !=
toupper(*str++))
return(1);
}
if (! *wildstr ) return(*str != 0);
if (*wildstr++ == wild_one) {
if (! *str++)
return(1); /* One char; skip */
} else { /* Found '*' */
if (!*wildstr)
return(0); /* '*' as last char: OK */
flag=(*wildstr != wild_many && *wildstr != wild_one);
do {
if (flag) {
char cmp;
if ((cmp= *wildstr) == wild_prefix && wildstr[1])
cmp=wildstr[1];
cmp=toupper(cmp);
while (*str && toupper(*str) != cmp)
str++;
if (!*str)
return(1);
}
if (wild_case_compare(0, str,wildstr) == 0)
return(0);
} while (*str++);
return(1);
}
}
return(*str != '\0');
} // end wild_case_compare
#else // MYSQL
// for mysql we just use what they have
#define extsql_malloc(a,b) my_malloc(a,b)
#define extsql_strdup(a,b) my_strdup(a,b)
static int extsql_output_prep_2_col(char *col1, char *col2, Protocol *protocol,
List- field_list, char *fill2);
static int extsql_output_2_col(char *col1Str, int col1Int, char *col2Str,
int col2Int, Protocol *protocol, char *fill1);
#endif // PGSQL or MYSQL build
#ifdef EXTSQL_50
#define net_printf net_printf_error
#define PROTOCOL Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF
#else
#define PROTOCOL 1
#endif
// remove this define if you are building from source, will skip license checks.
// #define EXTSQL_BINARY
#ifndef DBUG_RETURN
#define DBUG_RETURN(x) return(x)
#endif
//
// START ACTUAL EXTSQL FUNCTION DEFINITIONS
//
#ifdef PGSQL
int extsql_usage(THD* thd, DestReceiver *protocol) {
#ifdef NEVER__
}
#endif
#else
int extsql_usage(THD* thd) {
#endif
#ifdef PGSQL
TupOutputState *tstate;
TupleDesc tupdesc = NULL;
#else
Item* item;
List
- field_list;
Protocol *protocol = thd->protocol;
char *tstate = NULL, *tupdesc = '\0';
#endif
char* enableHelp = "Turn statistics on or off";
char* enableUsage = "STATISTICS ON|OFF";
char* resetHelp = "Reset statistics [reread conf file]";
char* resetUsage = "STATISTICS RESET ['conf file']";
char* readHelp = "Read saved statistics data";
char* readUsage = "STATISTICS READ [CONFIRM] 'data file'";
char* writeHelp = "Write out statistics data";
char* writeUsage = "STATISTICS WRITE 'data file'";
DBUG_ENTER("extsql_usage");
// return if not authorized, disabled or inactive
if (extsql_check(thd, 1)) DBUG_RETURN(1);
#ifdef PGSQL
if (extsql_output_prep_2_col("Function", "Usage", protocol, &tstate,
tupdesc)) {
DBUG_RETURN(1);
}
#else
if (extsql_output_prep_2_col("Function", "Usage", protocol, field_list,
tupdesc)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(enableHelp, 0, enableUsage, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
if (extsql_output_2_col(resetHelp, 0, resetUsage, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
if (extsql_output_2_col(readHelp, 0, readUsage, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
if (extsql_output_2_col(writeHelp, 0, writeUsage, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
extsql_output_complete(thd, tstate);
DBUG_RETURN(0);
}
// simple routine to turn extsql on or off using "STATISTICS ON|OFF"
// grammer
#ifdef PGSQL
int extsql_enable(THD* thd, DestReceiver *protocol, uint status) {
#ifdef NEVER__
}
#endif
#else
int extsql_enable(THD* thd, uint status) {
#endif
#ifdef PGSQL
TupOutputState *tstate;
TupleDesc tupdesc = NULL;
#else
Item* item;
List
- field_list;
Protocol *protocol = thd->protocol;
char *tstate = NULL, *tupdesc = '\0';
#endif
DBUG_ENTER("extsql_enable");
// return if not authorized, disabled or inactive
if (extsql_check(thd, 1)) DBUG_RETURN(1);
// accept any positive integer
if (status > 0) { status = 1; }
Shared->extsql_active = status;
#ifdef PGSQL
if (extsql_output_prep_2_col("extsql_active", "status", protocol, &tstate,
tupdesc)) {
DBUG_RETURN(1);
}
#else
if (extsql_output_prep_2_col("extsql_active", "status", protocol, field_list,
tupdesc)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(NULL, Shared->extsql_active, NULL, 1, protocol, tstate)) {
DBUG_RETURN(1);
}
extsql_output_complete(thd, tstate);
DBUG_RETURN(0);
}
// handler for "STATISTICS RESET"
#ifdef PGSQL
int extsql_reset(THD* thd, DestReceiver *protocol, const char* conf) {
#ifdef NEVER__
}
#endif
#else
int extsql_reset(THD* thd, const char* conf) {
#endif
#ifdef PGSQL
TupOutputState *tstate;
TupleDesc tupdesc = NULL;
#else
Item* item;
List
- field_list;
Protocol *protocol = thd->protocol;
char *tstate = NULL, *tupdesc = '\0';
#endif
int startType = STATS_INIT_RESET;
DBUG_ENTER("extsql_reset");
if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
net_printf(thd, 0, adminDisabled);
return(0);
}
// return if not authorized, disabled or inactive
if (extsql_check(thd, 0)) DBUG_RETURN(1);
// in this case, extsql_prep will parse and process the conf
// file.
if (conf != NULL) {
extsql_prep(&startType, conf, NULL);
if (startType == STATS_INIT_FAILURE) {
net_printf(thd, 0, "ExtSQL: Error reading config file");
DBUG_RETURN(1);
}
}
if (extsql_init(Shared->extsql_class_list, startType, Shared->extsql_reload_file)) {
net_printf(thd, 0, "ExtSQL: Reset failed");
DBUG_RETURN(1);
}
#ifdef PGSQL
if (extsql_output_prep_2_col("message", "status", protocol, &tstate,
tupdesc)) {
DBUG_RETURN(1);
}
#else
if (extsql_output_prep_2_col("message", "status", protocol, field_list,
tupdesc)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col("Statistics reset", 0, NULL, 1, protocol, tstate)) {
DBUG_RETURN(1);
}
extsql_output_complete(thd, tstate);
return 0;
}
// handler for "STATISTICS READ|WRITE"
#ifdef PGSQL
int extsql_file_io(THD* thd, DestReceiver *protocol, uint io_mode, uint confirm,
char* reload_file) {
#ifdef NEVER__
}
#endif
#else
int extsql_file_io(THD* thd, uint io_mode, uint confirm,
char* reload_file) {
#endif
#ifdef PGSQL
TupOutputState *tstate;
TupleDesc tupdesc = NULL;
#else
Item* item;
List
- field_list;
Protocol *protocol = thd->protocol;
char *tstate = NULL, *tupdesc = '\0';
#endif
int startType = STATS_INIT_RELOAD;
int prevState = Shared->extsql_active;
char* reloadFileCopy = extsql_strdup(reload_file, MYF(MY_WME));
char* message = extsql_strdup((io_mode ? "Reload file written successfully." :
"Reload file read successfully. Statistics are disabled. "),
MYF(MY_WME));
DBUG_ENTER("extsql_file-io");
if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
net_printf(thd, 0, adminDisabled);
return(0);
}
// return if not authorized, disabled or inactive
if (extsql_check(thd, 0)) DBUG_RETURN(1);
Shared->extsql_active = 0;
// writing file
if (io_mode) {
if (extsql_reload(STATS_WRITE_RELOAD, reloadFileCopy)) {
net_printf(thd, 0, "ExtSQL: Error encountered during reload WRITE");
Shared->extsql_active = prevState;
DBUG_RETURN(1);
}
Shared->extsql_active = 1;
// reading file
} else {
extsql_prep(&startType, NULL, reloadFileCopy);
if (startType == STATS_INIT_FAILURE) {
net_printf(thd, 0, "Error reading reload file or version incompatibility.");
Shared->extsql_active = prevState;
DBUG_RETURN(1);
}
if (startType == STATS_INIT_CONFIRM && !confirm) {
net_printf(thd, 0,
"Class data in reload file or hostname does not match. Use STATISTICS READ CONFIRM '%s' to override.",
reload_file);
Shared->extsql_active = prevState;
DBUG_RETURN(1);
}
if (extsql_init(Shared->extsql_class_list, STATS_INIT_RELOAD, reloadFileCopy)) {
net_printf(thd, 0, "ExtSQL: Read failed");
Shared->extsql_active = prevState;
DBUG_RETURN(1);
}
Shared->extsql_active = 0;
}
#ifdef PGSQL
if (extsql_output_prep_2_col("message", "status", protocol, &tstate,
tupdesc)) {
DBUG_RETURN(1);
}
#else
if (extsql_output_prep_2_col("message", "status", protocol, field_list,
tupdesc)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(message, 0, NULL, 1, protocol, tstate)) {
DBUG_RETURN(1);
}
extsql_output_complete(thd, tstate);
return 0;
}
// convenience function to check user authorization
// checks to see if the specified 'user' is in the string containing
// authorized users 'authUsers', set by admin with extsql_users=".."
// return 1 if authorized, 0 otherwise
static int extsql_user_auth(char *user, char *authUsers) {
char *tPtr, *token;
char tmpUser[STATS_TMP_BUFF_SIZE];
int found;
DBUG_ENTER("extsql_user_auth");
// we search allowed users each time in case list changes
if (strlen(authUsers) >= STATS_TMP_BUFF_SIZE-1) {
sql_print_error("ExtSQL: user list has exceeded storage limits");
return(0);
}
strcpy(tmpUser, authUsers);
tPtr = tmpUser;
found = 0;
while ( (token = strtok(tPtr, ", ")) ) {
tPtr = NULL;
if (!strcmp(token, user) || !strcmp("root", user)) { // match
found = 1;
break;
}
} // end while - more user names
if (!found) {
return(0);
}
return(1); // they are okay!
} // end extsql_user_auth
// Check for authorized user, enabled and active extsql
int extsql_check(THD* thd, int overrideActive) {
char *user;
DBUG_ENTER("extsql_check");
if (Shared->extsql_disabled) { // just get out
net_printf(thd, 0, "ExtSQL: disabled");
DBUG_RETURN(1);
}
if (!(Shared->extsql_active || overrideActive)) { // just get out
net_printf(thd, 0, "ExtSQL: deactivated");
DBUG_RETURN(1);
}
#ifdef PGSQL
user = MyProcPort->user_name;
#else
#ifdef EXTSQL_50
user = thd->security_ctx->user;
#else
user = thd->user;
#endif
#endif
// are they authorized
if (!extsql_user_auth(user, extsql_users)) {
// let the DBA know just in case.
sql_print_warning("ExtSQL: attempted access by user: %s", user);
net_printf(thd, 0, "ExtSQL: access not allowed to user: %s",
user);
DBUG_RETURN(1);
}
DBUG_RETURN(0);
}
// generic function to do output prep, identify column headers, types
// INPUT: col1 and col2 (col2 can be NULL)
// non-zero return on failure, one or two columns
// NOTE dest,tstate, and tupdesc are declared in the calling function,
// same vars will be used in call to extsql_output_2_col
// fill1 and fill2 are placeholders to keep function calls the same
int extsql_output_prep_2_col(char *col1, char *col2
#ifdef PGSQL
,DestReceiver *dest, TupOutputState **tstate, TupleDesc tupdesc
#else
,Protocol *protocol, List
- field_list, char *fill2
#endif
) {
if (!col1) {
sql_print_error("ExtSQL: extsql_output_prep_2_col missing col1.");
return(1);
}
#ifdef PGSQL
// tuple descriptor for up to two cols
if (col2) {
tupdesc = CreateTemplateTupleDesc(2, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, col1,
TEXTOID, -1, 0, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, col2,
TEXTOID, -1, 0, false);
} else {
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, col1,
TEXTOID, -1, 0, false);
}
// prepare for projection of tuples
*tstate = begin_tup_output_tupdesc(dest, tupdesc);
#else
Item *item;
// column headers
field_list.push_back(item = new Item_empty_string(col1, MAX_VAR_SIZE));
if (col2) {
field_list.push_back(item = new Item_empty_string(col2, MAX_VAR_SIZE));
}
// output the column headers
if (protocol->send_fields(&field_list, PROTOCOL)) {
sql_print_error("ExtSQL: output_prep_2_col protocol write failed");
return(1);
}
#endif
return(0);
} // end extsql_output_prep_2_col
// generic function to send output rows (up to 2 cols, char data)
// INPUT: col1Str (value), col2Str maybe NULL
// NOTE tstate is declared in the calling function,
// same vars will be used in call to extsql_output_prep_2_col
// null strings will result in using integer values
int extsql_output_2_col(char *col1Str, int col1Int, char *col2Str, int col2Int
#ifdef PGSQL
,DestReceiver *dest, TupOutputState *tstate
#else
,Protocol *protocol, char *fill1
#endif
) {
bool cleanup1 = false;
bool cleanup2 = false;
#ifdef PGSQL
char *values[2];
#endif
// Convert ints to char*
if (col1Str == NULL) {
cleanup1 = true;
col1Str = (char*)my_malloc(8 * sizeof (char), MYF(MY_WME | MY_ZEROFILL));
sprintf(col1Str, "%d", col1Int);
}
if (col2Str == NULL) {
cleanup2 = true;
col2Str = (char*)my_malloc(8 * sizeof (char), MYF(MY_WME | MY_ZEROFILL));
sprintf(col2Str, "%d", col2Int);
}
#ifdef PGSQL
// assign to the values array ZZ - not done
if (col1Str) {
values[0] = col1Str;
}
if (col2Str) {
values[1] = col2Str;
}
// send it to dest
do_tup_output(tstate, values);
#else
protocol->prepare_for_resend();
if (col1Str) {
protocol->store(col1Str, strlen(col1Str), system_charset_info);
} else { // must be int
protocol->store((longlong) col1Int);
}
if (col2Str) {
protocol->store(col2Str, strlen(col2Str), system_charset_info);
} else { // must be int
protocol->store((longlong) col2Int);
}
if (protocol->write()) {
sql_print_error("ExtSQL: output_2_col protocol write failed");
return(1);
}
#endif
// cleanup dynamic memory allocation
if (cleanup1) { my_free(col1Str, MYF(0)); }
if (cleanup2) { my_free(col2Str, MYF(0)); }
return(0);
} // end extsql_output_2_col
// generic function to handle transmission end
void extsql_output_complete(
#ifdef PGSQL
THD *thd, TupOutputState *tstate
#else
THD *thd, char *tstate
#endif
) {
#ifdef PGSQL
end_tup_output(tstate);
#else
send_eof(thd);
#endif
} // end extsql_output_complete
// called from the parser in response to a SHOW STATISTICS command.
// wild is what is given in the LIKE 'wild'
int mysql_show_extsql(THD *thd,
const char *stats_class,
#ifdef PGSQL
DestReceiver *protocol,
List *stats_vars,
#else
List &stats_vars,
#endif
uint stats_hourly,
const char *wild,
#ifdef PGSQL
char *stats_order_var,
#else
const char *stats_order_var,
#endif
const uint stats_order_dir,
const uint stats_order_limit,
#ifdef PGSQL
char *stats_where_var,
#else
const char *stats_where_var,
#endif
const uint stats_where_num,
const uint stats_where_char)
{
#ifdef PGSQL
#define VALUE_LIMIT 100
TupOutputState *tstate;
TupleDesc tupdesc = NULL;
#ifdef PGSQL_8
ListCell *item;
#else
List *item;
#endif
int listCount;
char *values[MAX_VARS_IN_CLASS], *valuesPtr[VALUE_LIMIT], valueBuff[256]; // output
int ptrIndex = 0;
int system_charset_info = 0; // not used in PGSQL, just placeholder, call our func.
#else
Item *item;
List
- field_list; // has column headings, all strings.
List check_vars;
Protocol *protocol= thd->protocol;
LEX_COLUMN *column;
char *tstate = NULL; char *tupdesc = '\0'; // placeholders for tstate and tupdesc
#endif
char *columnName; // used in output for MySQL PGSQL
int res = 0;
int namedVars = 0; // are they asking for specific vars
char listBuff[MAX_BUFF_SIZE], timeBuff[DATE_SIZE];
char *varListPtr = listBuff;
int timeLimit = 0, currentTime = 0, indexTime = 0, varIndex = 0;
char *varName, *termChar;
int varNameLength = 0, timeCount = 0, rowCount = 0, whereVarAddedList = 0;
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **statPtr;
int i, maxVar, maxTime, instance, maxInstance, var, found=0, value = 0, skip = 0,
rowHasData;
char *user;
MYSQL_TIME mysql_time;
#ifdef EXTSQL_50
user = thd->security_ctx->user;
#else
#ifdef PGSQL
user = MyProcPort->user_name;
#else
user = thd->user;
#endif
#endif
DBUG_ENTER("mysql_show_extsql");
// CHECK FOR DATA DUMPS OR TESTS
if STATS_DEBUG(STATS_DUMP) {
extsql_stats_dump();
extsql_thread_dump(thd, 1);
} else if STATS_DEBUG(STATS_DUMP_ONCE) { // just once
extsql_stats_dump();
extsql_thread_dump(thd, 1);
Shared->extsql_debug -= STATS_DUMP_ONCE; // clear
}
if STATS_DEBUG(STATS_TEST_ONE) {
Shared->extsql_active = 0;
Shared->extsql_debug -= STATS_TEST_ONE; // clear
extsql_stats_dump();
if (extsql_reload(STATS_WRITE_RELOAD, Shared->extsql_reload_file)) {
sql_print_error("ExtSQL: Error encountered during reload WRITE");
}
extsql_stats_dump();
Shared->extsql_active = 1;
}
if STATS_DEBUG(STATS_TEST_TWO) {
Shared->extsql_debug -= STATS_TEST_TWO; // clear
extsql_stats_dump();
if (0) {
extsql_init(Shared->extsql_class_list, STATS_INIT_RELOAD, Shared->extsql_reload_file);
} else {
extsql_init(Shared->extsql_class_list, STATS_INIT_RESET, Shared->extsql_reload_file);
}
extsql_stats_dump();
} // end if-else testing
// return if not authorized, disabled or inactive
if (extsql_check(thd, 0)) DBUG_RETURN(1);
// okay, statistics are active, prepare for output.
if STATS_DEBUG(STATS_SHOW_PARAMS) {
sql_print_information("ExtSQL: internal variables class %s,\
hourly %d, wild %s, ORDER BY %s, direction %d, LIMIT %d, \
WHERE var %s, compare %d, value %d",
stats_class, stats_hourly, wild,
stats_order_var,
stats_order_dir, stats_order_limit,
stats_where_var,
stats_where_char, stats_where_num);
}
if (stats_class == NULL) { // they want help/status
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **statPtr;
char *Usage = "Usage";
char *Help = "SHOW STATISTICS (* | Var[,Var]) FROM Class [LIKE 'Instance']";
char *Help2 = " [WHERE Var ('<'|'>'|'=') num] [ORDER BY Var] [HISTORY] [LIMIT rows_or_time]";
#ifdef EXTSQL_50
char *Help3 = "also available from INFORMATION_SCHEMA, SHOW TABLES FROM INFORMATION_SCHEMA for list.\
ExtSQL reported as part of EXTSTATS_(Class) tables.";
#endif
char *Version = "Version";
#ifdef EXTSQL_BINARY
char *Key = "License Key";
#endif
char *Users = "Stat Users";
char *Blank = " ";
int maxVar, maxTime, maxInstance, var, instance, classCount;
#ifdef PGSQL
if (extsql_output_prep_2_col("Item", "Value", protocol, &tstate, tupdesc)) {
DBUG_RETURN(1);
}
#else
if (extsql_output_prep_2_col("Item", "Value", protocol, field_list, tupdesc)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(Usage, 0, Help, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
if (extsql_output_2_col(Usage, 0, Help2, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
#ifdef EXTSQL_50
if (extsql_output_2_col(Usage, 0, Help3, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(Version, 0, Shared->extsql_version+1, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
#ifdef EXTSQL_BINARY
if (extsql_output_2_col(Key, 0, extsql_key, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
#endif
if (extsql_output_2_col(Users, 0, extsql_users, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
if STATS_DEBUG(STATS_PID) { // show the pid of the server for gdb
sprintf(listBuff, "(%d)", getpid());
if (extsql_output_2_col("Server PID", 0, listBuff, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
}
// now we show them the class list stuff
classCount = 0;
for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
maxVar = stats_class_data->maxVar;
maxTime = stats_class_data->maxTime-1;
maxInstance = stats_class_data->maxInstance;
if (extsql_output_2_col(Blank, 0, Blank, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
// how many instances are in use
for (instance=0;
instance < maxInstance && stats_class_data->instanceIndex[instance];
instance++) {
}
sprintf(listBuff, "Max instances/in use (%d/%d), Max vars (%d), Max time (%d), Time units(%c)",
maxInstance, instance, maxVar, maxTime, stats_class_data->timeUnits);
if (extsql_output_2_col(stats_class_data->name, 0, listBuff, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
strcpy(listBuff, " ");
for (var=0, statPtr = stats_class_data->varIndex;
(var < maxVar) && *statPtr ;
var++, statPtr++) {
sprintf(listBuff+strlen(listBuff),"%s ", (*statPtr)->name);
}
if (extsql_output_2_col(stats_class_data->name, 0, listBuff, 0, protocol, tstate)) {
DBUG_RETURN(1);
}
} // end for - all clasees
extsql_output_complete(thd, tstate);
DBUG_RETURN(0);
} // end if - just showing help
// NOTE - normal RETURN ONLY here!
// find the instance list for the class we are looking for
found = 0;
for (stats_class_data = Shared->statData; stats_class_data->maxInstance;
stats_class_data++) {
if (!strcmp(stats_class_data->name, stats_class)) { // found it
found = 1;
break;
}
} // end for
if (!found) {
net_printf(thd, 0, "ExtSQL: can't find requested class %s, enter SHOW STATISTICS for usage.",
stats_class);
DBUG_RETURN(1);
}
// AT THIS POINT stats_class_data points to the requested class
maxVar = stats_class_data->maxVar;
maxTime = stats_class_data->maxTime;
maxInstance = stats_class_data->maxInstance; // needed for array indexing
// NOTE - a LOT of checking first to make sure inputs make sense.
if (stats_order_var) { // they used ORDER BY
net_printf(thd, 0, "ExtSQL: ORDER BY not supported in this version.");
DBUG_RETURN(1);
}
// CHECK that the limit is NOT larger than allowed if done for time
if (stats_hourly && stats_order_limit >= maxTime) {
net_printf(thd, 0, "ExtSQL, limit of %d is greater than stored time for this class of %d.",
stats_order_limit, maxTime-1);
DBUG_RETURN(1);
}
// CHECK BLOCK - check the where character
if (stats_where_char) {
switch (stats_where_char) {
case '>':
case '<':
case '=':
break;
default:
net_printf(thd, 0, "ExtSQL: unsupported comparison (%c), enter SHOW_STATISTICS for usage.", stats_where_char);
DBUG_RETURN(1);
} // end switch
}
// Set listCount to the number of Var column headings we will have
#ifdef PGSQL
// For PGSQL we need to count the columns what we have ahead of time
listCount = 0; namedVars = 0;
foreach(item, stats_vars) {
char *var1 = strVal(lfirst(item));
if (!strcmp(var1, "statistics") ) { // ZZ - not really best way to check, default list
// loop through for count
for (var=0, statPtr = stats_class_data->varIndex;
(var < stats_class_data->maxVar) && *statPtr ;
var++, statPtr++) {
listCount++;
}
break;
}
listCount++;
namedVars = 1; // redundant
} // end for - all vars in list
#else
if (stats_vars.elements) {
namedVars = 1;
}
#endif
// CHECK BLOCK - if they gave us specific columns (namedVars=1) to display,
// check those vars are actually being tracked
if (namedVars) {
#ifdef PGSQL
foreach(item, stats_vars) { // output the column heading
columnName = strVal(lfirst(item));
#else
List_iterator stats_column(stats_vars);
while ((column=stats_column++)) { // search for a match in class
columnName = (char *)column->column.ptr();
#endif
found = 0;
for (var = 0, statPtr = stats_class_data->varIndex;
(var < maxVar) && *statPtr ;
var++, statPtr++) {
if (strlen(columnName) > MAX_VAR_SIZE - 5) {
net_printf(thd, 0, "ExtSQL: Var is too long");
DBUG_RETURN(1);
}
if (!strcasecmp((*statPtr)->name, columnName)) { // ZZ - low case input in postgres?
found = 1;
break;
}
} // end for - all vars
if (!found) {
net_printf(thd, 0, "ExtSQL: can't find Var %s, SHOW STATISTICS for usage.",
columnName);
DBUG_RETURN(1);
}
} // end while/for - more columns
#ifdef NEVER__
} // to keep indent level correct in emacs
#endif
} // end if - named vars are being tracked.
// CHECK BLOCK - if we have an ORDER by or WHERE clause variable, check those also
if (stats_where_var || stats_order_var) {
#ifdef PGSQL
List *checkList;
if (stats_where_var && stats_order_var) {
checkList = (List *) list_make2(makeString(stats_where_var), makeString(stats_order_var));
} else if (stats_where_var) {
checkList = (List *) list_make1(makeString(stats_where_var));
} else {
checkList = (List *) list_make1(makeString(stats_order_var));
}
foreach(item, checkList) { // check the list
columnName = strVal(lfirst(item));
#else
if (stats_where_var) {
String *tmpStr = new (thd->mem_root) String ((const char *)stats_where_var, strlen(stats_where_var), system_charset_info);
check_vars.push_back(new LEX_COLUMN (*tmpStr, 0));
}
if (stats_order_var) {
String *tmpStr = new (thd->mem_root) String ((const char *)stats_order_var, strlen(stats_where_var), system_charset_info);
check_vars.push_back(new LEX_COLUMN (*tmpStr, 0));
}
List_iterator stats_column(stats_vars);
while ((column=stats_column++)) { // search for a match in class
columnName = (char *)column->column.ptr();
#endif
found = 0;
for (var = 0, statPtr = stats_class_data->varIndex;
(var < stats_class_data->maxVar) && *statPtr ;
var++, statPtr++) {
// make sure the name isn't too long
if (strlen(columnName) > MAX_VAR_SIZE - 5) {
net_printf(thd, 0, "ExtSQL: Var (%s) is too long, check entry.",
columnName);
DBUG_RETURN(1);
}
if (!strcasecmp((*statPtr)->name, columnName)) {
found = 1;
break;
}
} // end for - all vars
if (!found) {
net_printf(thd, 0, "ExtSQL: can't find Var %s, enter SHOW STATISTICS for usage.",
columnName);
DBUG_RETURN(1);
}
} // end while - more columns
#ifdef NEVER__
} // to keep indent level correct in emacs
#endif
} // end if - specified where or order by var
// START OUTPUT - prepare and output the header columns
// The class name (always FIRST column)
// PGSQL uses a predefined array of column headers, need to keep count
// MYSQL uses a stack approach.
#ifdef PGSQL
if (stats_hourly) {
listCount += 2; // class name and time
} else {
listCount++; // add just one for the class name
}
i = 1; // current param position
tupdesc = CreateTemplateTupleDesc(listCount, false);
TupleDescInitEntry(tupdesc, (AttrNumber) i++, stats_class, TEXTOID, -1, 0, false);
#else
field_list.push_back(item = new Item_empty_string(stats_class, STATS_MAX_NAME));
#endif
// if HOURLY is asked for, it is always the SECOND column
if (stats_hourly) {
char *units;
switch (stats_class_data->timeUnits) {
case 'm':
units = "minutes";
break;
case 'h':
units = "hours";
break;
case 'd':
units = "days";
break;
default:
units = "UNKNOWN";
}
#ifdef PGSQL
TupleDescInitEntry(tupdesc, (AttrNumber) i++, units, TEXTOID, -1, 0, false);
#else
field_list.push_back(item = new Item_empty_string(units, 30)); // max size of 'minutes'
#endif
}
// Then the variable list, this will either be what they specified, or
// the conf list. We do a SPECIAL check to see if they have included a
// WHERE clause. If they have, but it is not included in the column list
// we will add it, but it will not be displayed on output
// LARGE else block here depending on whether they specified list, or we use default!
strcpy(varListPtr,","); // init the list
if (namedVars) { // they identified the columns they want
char tempName[MAX_VAR_SIZE]; // ZZ
#ifdef PGSQL
foreach(item, stats_vars) { // output the column heading
columnName = strVal(lfirst(item));
#else
List_iterator stats_column2(stats_vars);
while ((column=stats_column2++)) { // search for a match in class
columnName = (char *)column->column.ptr();
#endif
if (strlen(columnName) > MAX_VAR_SIZE - 5) {
net_printf(thd, 0, "ExtSQL: Var is too long");
DBUG_RETURN(1);
}
#ifdef PGSQL
TupleDescInitEntry(tupdesc, (AttrNumber) i++, columnName, TEXTOID, -1, 0, false);
#else
field_list.push_back(item = new Item_uint(columnName, MY_INT64_NUM_DECIMAL_DIGITS ));
#endif
strcat(varListPtr, columnName);
strcat(varListPtr, ",");
} // end while - more columns
#ifdef NEVER__
} // to keep indent level correct in emacs
#endif
// is the where var on the list
if (stats_where_var) {
if (strlen(stats_where_var) > MAX_VAR_SIZE - 5) {
net_printf(thd, 0, "ExtSQL: WHERE Var too long");
DBUG_RETURN(1);
}
strcpy(tempName, ",");
strcat(tempName, stats_where_var);
strcat(tempName, ",");
if (strstr(varListPtr,tempName)) {
whereVarAddedList = 0;
} else {
whereVarAddedList = 1;
strcat(varListPtr, stats_where_var);
strcat(varListPtr,",");
}
} // end if - where clause
} else { // we output default column headings and create varListPtr
// loop through the var names for headings
for (var=0, statPtr = stats_class_data->varIndex;
(var < stats_class_data->maxVar) && *statPtr ;
var++, statPtr++) {
#ifdef PGSQL
TupleDescInitEntry(tupdesc, (AttrNumber) i++, (*statPtr)->name, TEXTOID, -1, 0, false);
#else
field_list.push_back(item = new Item_uint((*statPtr)->name, MY_INT64_NUM_DECIMAL_DIGITS ));
item->maybe_null=1;
#endif
strcat(varListPtr,(*statPtr)->name );
strcat(varListPtr, ",");
// check to make sure we don't bust string
if (strlen(varListPtr) > MAX_BUFF_SIZE - 50) {
net_printf(thd, 0, "ExtSQL: Var list too long");
DBUG_RETURN(1);
}
} // end for - all instances
} // end if - use defaults
// output the column headers
#ifdef PGSQL
tstate = begin_tup_output_tupdesc(protocol, tupdesc);
#else
if (protocol->send_fields(&field_list, PROTOCOL)) {
DBUG_RETURN(1);
}
#endif
// AT THIS POINT the output columns are in varListPtr
// next, if they will be asking for hourly output, we prepare an array
// of hour indexes.
if STATS_DEBUG(STATS_SHOW_PARAMS) {
sql_print_information("ExtSQL: varListPtr is (%s), whereVarAddedList(%d)",
varListPtr, whereVarAddedList);
}
timeLimit = 1; // by default we only show one time.
currentTime = stats_class_data->time; // time we are currently logging to.
if (stats_hourly) { // reset the proper inner loop limits
if (stats_order_limit) {
timeLimit = stats_order_limit; // whatever thy wanted
} else {
timeLimit = maxTime-1; // they get it all
} // end if else - time limit
} // end if - user wants hours displayed.
// AT THIS POINT - we have the varListPtr area with the oolumns in the
// display order we want. Remember, zero index holds the total. The
// currentTime has our present location and it is circular, i.e. if
// maxTime for the class is 4, we store current + 3. If the currentTime
// is 2, the proper way to display all hours, starting with the most
// recent is to show 2(current),1(prior),3(more prior) - skip 0
// the currentTime has no correlation to actual clock time, it is just
// a position within a circular buffer of times, each class could have
// a different limit.
// START outputing data for the every instance within the matching class
// we now go through all the instances.
for (instance=0; instance < maxInstance && stats_class_data->instanceIndex[instance] ;
instance++) {
if (wild && wild_case_compare(system_charset_info,
stats_class_data->instanceIndex[instance], wild) ) {
continue; // skip this one
} // end if - wild match
// are we over our output LIMIT
if (!stats_hourly && stats_order_limit) { // not doing times, put limit on general output
if (rowCount >= stats_order_limit) {
break;
}
}
// loop through the times, if stats_hourly is not set we just print the stuff for zero
// time, the grand totals
// if stats_timely is set we will output a number of hours starting at currentTime, and
// limited by timeLimit (starts at 1).
for (timeCount = 0, indexTime = stats_hourly ? currentTime : 0 ;
timeCount < timeLimit && indexTime >= 0;
timeCount++, indexTime-- ) {
// if we are outputing times, we have to adjust our indexTime
if (stats_hourly && !indexTime) { // we have gone back to the total
indexTime = maxTime - 1; // pick the last
}
// prepare the data row
skip = 0;
#ifdef PGSQL
// nothing special required here
// NOTE, variable i keeps our index in values array
i = 0;
#else
protocol->prepare_for_resend();
#endif
res = 0;
rowHasData = 0; // do we have anything to show, only on HISTORY (stats_hourly set)
// first column - instance name - always
#ifdef PGSQL
values[i++] = stats_class_data->instanceIndex[instance];
#else
protocol->store(stats_class_data->instanceIndex[instance],
strlen(stats_class_data->instanceIndex[instance]), system_charset_info);
#endif
// if we are doing times, second column is times - always, we use indexTime
if (stats_hourly) {
value = (int) stats_class_data->timeLabels[indexTime];
// convert to local time and then to a string for output
#ifdef PGSQL
// ZZ need time conversion/zone
mysql_time = value;
#else
thd->variables.time_zone->gmt_sec_to_TIME(&mysql_time, (my_time_t) value);
#endif
my_datetime_to_str(&mysql_time, timeBuff);
// we normally chop seconds off the time, from: 2008-04-04 14:59:48 TO 2008-04-04 14:59
if (!STATS_DEBUG(STATS_TIME_DETAIL)) {
timeBuff[strlen(timeBuff) - 3] = '\0';
}
#ifdef PGSQL
values[i++] = timeBuff;
#else
protocol->store(timeBuff, strlen(timeBuff), system_charset_info);
#endif
} // end if - hourly
// loop through varListPtr, output the vars in the proper order
// varListPtr = ",bytes,selects," varName will move through this
for ( varName = varListPtr; *varName ; varName = strstr(varName, ",")) {
varName++; // move off the comma
//sql_print_information(" target varName is (%s)", varName);
if (! *varName) { // we are done
break;
}
varNameLength = strchr(varName, ',') - varName;
// okay, varName points at a name, lets go through the index looking
// for a match
found = 0;
value = 0;
for (varIndex=0, statPtr = stats_class_data->varIndex;
varIndex < maxVar && *statPtr;
varIndex++, statPtr++) {
termChar = (*statPtr)->name + varNameLength +1; // point to end of the current var name
if (!strncasecmp( (*statPtr)->name, varName, varNameLength)) {
found = 1;
value = stats_class_data->data [ARRAY_INDEX(instance, varIndex, maxVar,
indexTime, maxTime)];
// before we send, check the value if they have set a WHERE condition
if (stats_where_var && !strncasecmp((*statPtr)->name, stats_where_var, varNameLength)) { // match
switch (stats_where_char) {
case '>':
if (value <= stats_where_num) skip = 1; // this row is no good
break;
case '<':
if (value >= stats_where_num) skip = 1; // this row is no good
break;
case '=':
if (value != stats_where_num) skip = 1; // this row is no good
break;
default:
sql_print_warning("ExtSQL: unsupported comparison (%d)", stats_where_char);
} // end switch
rowHasData = 1;
} // end if - match using WHERE clause
if (skip) {
break;
}
if (whereVarAddedList && !strncasecmp((*statPtr)->name, stats_where_var, varNameLength)) {
break; // don't output the where column, they didn't ask for it
}
#ifdef PGSQL
// convert to text, store ptr addr and then free
// after send
sprintf(valueBuff, "%d", value);
values[i] = strdup(valueBuff);
valuesPtr[ptrIndex++] = values[i++]; // need to free after output
if (ptrIndex >= VALUE_LIMIT) {
sql_print_error("ExtSQL, output value limit reached");
DBUG_RETURN(1);
}
#else
protocol->store((longlong) value);
#endif
if (value) { // is it non-zero
rowHasData = 1;
}
break;
} // end if - matching
} // end for - a matching var in the list
if (skip) {
break;
}
if (!found) {
#ifdef PGSQL
values[i++] = "n/a";
#else
value=999999;
protocol->store((longlong) value);
#endif
}
} // end for - requested values
if (skip || (stats_hourly && !rowHasData)) { // free it up!
// if PGSQL we allocate some value space above, make sure we free it in all situations.
#ifdef PGSQL
if (ptrIndex) {
for (ptrIndex--; ptrIndex; ptrIndex--) {
if (valuesPtr[ptrIndex]) {
free(valuesPtr[ptrIndex]);
}
}
}
#endif
continue;
}
// send the row to the user,
#ifdef PGSQL
do_tup_output(tstate, values);
if (ptrIndex) {
for (ptrIndex--; ptrIndex; ptrIndex--) {
if (valuesPtr[ptrIndex]) {
free(valuesPtr[ptrIndex]);
}
}
}
#else
if (protocol->write()) {
sql_print_error("ExtSQL, protocol write failed");
DBUG_RETURN(1);
}
#endif
rowCount++; // keep track of the count if have a general LIMIT in effect.
} // end for - all requested times
} // end for - all instances
extsql_output_complete(thd, tstate);
DBUG_RETURN(0);
} // end mysql_show_statistics
// record a statistic_increment call
void extsql_inc(ulong *V, ulong C)
{
#ifdef PGSQL
THD *thd = MyProc;
time_t log_sec, now_sec;
#else
THD *thd = current_thd;
my_time_t log_sec, now_sec;
#endif
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **var_ptr;
int i, var, offset, offsetLimit, classCount, found = 0, instance, maxVar, maxTime;
int threadID, command;
char *user = "", *host = "", *db = "", *proc_info = "";
int *statVerify = 0, *indexPtr = 0;
if (!Shared->extsql_active || Shared->extsql_disabled || STATS_DEBUG(STATS_DISABLE_INC)) {
return;
}
#ifdef EXTSQL_50
// during init we stored just offsets from THD structure status_var to each stat
// for nonglobal items -- we got an address in V, need to determine if it is a real global
// or something incremented as part of a thread. We check to see if it is in the
// address range of the status_var structure of the current thread
char *off;
off = (char *) V;
if (off >= (char *)&(thd->status_var) && off < (char *)&(thd->status_var)+sizeof(thd->status_var)) {
V = (ulong *)(off - (char *)&(thd->status_var)) ;
if STATS_DEBUG(STATS_INC_VAR) sql_print_information(" altered to: 0x%x, base: 0x%x",
V, &(thd->status_var));
}
#endif
// check against the list of Vars we are tracking
for (i = 0; i < Shared->varAddrCount; i++) {
if (Shared->varAddrTrackArray[i] == (char *) V) { // one we are tracking
found = 1;
break;
}
} // end for - all tracked vars
if (!found) {
return;
}
#ifndef PGSQL
// ZZ ignore slow_launch_threads and early byte counts - unstable at this point
if (V == (ulong *) &slow_launch_threads || thd->command == COM_CONNECT) {
return;
}
#endif
// input checks
if (!thd
#ifndef PGSQL
|| !thd->thread_id
#endif
) { // don't do thread zero for now
if STATS_DEBUG(STATS_INC_PARAMS) {
sql_print_information("ExtSQL: Got null thread or thd 0");
}
return;
}
extsql_init_proc_vars(&threadID, &command, &user, &db, &host, &proc_info, &indexPtr, &statVerify);
// we need something for these values
if (!user) {
user="(null)";
}
if (!host) {
host="(null)";
}
if (*statVerify != STATS_MAGIC_NUM) {
Shared->extsql_counter++; // ZZ - how many do we get?
return;
} // end if - badmagic
// too many warnings, turn it off
if (Shared->extsql_counter > STATS_WARNING_LIMIT) {
Shared->extsql_active = 0;
sql_print_warning("ExtSQL: deactivated due to high internal warning count(%d), dumping internals",
(int)Shared->extsql_counter);
extsql_stats_dump();
return;
}
if STATS_DEBUG(STATS_THREAD_CHK) {
extsql_thread_dump(thd, 0); // do a check of indexes, no output unless error
}
// we get time in seconds, for performance we only call localtime once/minute to do the min,hour,day conversions
// could use thd->start_time, always avail? ZZ
now_sec = time(NULL);
if (Shared->lastMin != (int)(now_sec/60)) { // we rolled over
// let's try and get the lock, if we fail, someone else doing rollover
if (pthread_mutex_trylock(&Shared->LOCK_stats_clock)) {
// nothing to do
} else {
struct tm now_date;
localtime_r(&now_sec, &now_date);
Shared->lastMin = (int)(now_sec/60);
// check all time for classes
classCount = 0;
for (stats_class_data = Shared->statData;
classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
int now_time;
switch (stats_class_data->timeUnits) {
case 'm':
now_time = now_date.tm_min;
break;
case 'h':
now_time = now_date.tm_hour;
break;
case 'd':
now_time = now_date.tm_mday;
break;
default:
now_time = now_date.tm_hour;
Shared->extsql_active = 0;
sql_print_error("ExtSQL: deactivated, got invalid time unit (%d)", stats_class_data->timeUnits);
#ifdef PGSQL
pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
return;
}
if (now_time != stats_class_data->lastTime) { // rolled over
stats_class_data->lastTime = now_time;
// increment time and do checks
stats_class_data->time++;
if (stats_class_data->time >= stats_class_data->maxTime) { // reset to start
stats_class_data->time = 1; // zero index is used for totals
}
// create label, MM/DD HH:MM
if STATS_DEBUG(STATS_TIME_CHANGE) {
sql_print_information("ExtSQL, class %s, time change: cur clock time is %d, time index s %d",
stats_class_data->name, now_time, stats_class_data->time);
}
// reset counters for that time to zero for all variables
for (instance=0, maxVar = stats_class_data->maxVar, maxTime = stats_class_data->maxTime;
instance < stats_class_data->maxInstance; instance++) {
for (var=0; var < maxVar; var++) {
offset = ARRAY_INDEX(instance, var, maxVar, stats_class_data->time, maxTime);
offsetLimit = stats_class_data->dataEnd - stats_class_data->data;
// check to make sure offset isn't out of limits
if (offset < 0 || offset >= offsetLimit ) {
sql_print_warning("ExtSQL: warning, TIME reset offset of 0x%x (%d,%d,%d,%d,%d) for 0x%x class %s of thd %d/%s exceeds bound of 0x%x(0x%x)",
offset, instance, var, maxVar, stats_class_data->time, maxTime,
(uint)V, stats_class_data->name, threadID, command_name[command], offsetLimit, *statVerify);
Shared->extsql_counter++;
extsql_thread_dump(thd, 1);
#ifdef PGSQL
pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
return;
}
stats_class_data->data[offset] = 0;
// store unix time
// we normally round it off to minutes/hours/days depending on units
if (!STATS_DEBUG(STATS_TIME_DETAIL)) {
switch (stats_class_data->timeUnits) {
case 'm':
log_sec = now_sec - (now_sec % 60);
break;
case 'h':
log_sec = now_sec - (now_sec % 3600);
break;
case 'd':
log_sec = now_sec - (now_sec % 3600);
break;
default:
now_time = now_date.tm_hour;
Shared->extsql_active = 0;
sql_print_error("ExtSQL: deactivated, got invalid time unit (%d)", stats_class_data->timeUnits);
return;
}
} else {
log_sec = now_sec;
} // end if/else - don't round off time
stats_class_data->timeLabels[stats_class_data->time] = log_sec;
} // end for - vars
} // end for - instances
} // end if - new time
} // end for - all classes
#ifdef PGSQL
pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
} // end if-else - got the lock
} //end if - time rolled over
// ready to increment value
if STATS_DEBUG(STATS_INC_VAR) sql_print_information("ExtSQL: 0x%x by %d", (uint)V,(int)C);
// look for the value being tracked in all classes
classCount = 0;
for (stats_class_data = Shared->statData;
classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
var_ptr = stats_class_data->varIndex;
for (var = 0, maxVar = stats_class_data->maxVar, maxTime = stats_class_data->maxTime
; var < maxVar ; var_ptr++,var++) {
if ((*var_ptr)->addr == (char *)V) { // we are tracking it
i = indexPtr[(int)classCount];
if (i < 0 || i >= stats_class_data->maxInstance) { // bound exceeded
sql_print_warning("ExtSQL: index (%d) bad value (0x%x/0x%x), V(0x%x), classCount(%d), thd(%d/%s/%s) user(%s), host(%s) db(%s) magic(0x%x)",
classCount, i, stats_class_data->maxInstance, (uint) V, classCount,
threadID, proc_info, command_name[command],
user, host, db, *statVerify);
Shared->extsql_counter++;
extsql_thread_dump(thd, 1);
return;
}
// increment it for the class for the 0 (summary time) and the current time
offset = ARRAY_INDEX(i, var, maxVar, stats_class_data->time, maxTime);
offsetLimit = stats_class_data->dataEnd - stats_class_data->data;
if STATS_DEBUG(STATS_INC_VAR) {
sql_print_information("ExtSQL, class %s, i (%d) incrementing %s by (%d), offset/Limit(%d/%d)",
stats_class_data->name, i, (*var_ptr)->name, (int) *V, offset, offsetLimit);
}
// check to make sure offset isn't out of limits
if (offset < 0 || offset >= offsetLimit ) {
sql_print_warning("ExtSQL: warning, DATA inc offset of 0x%x (%d,%d,%d,%d,%d) for 0x%x class %s of thd %d/%s exceeds bound of 0x%x(0x%x)",
offset, i, var, maxVar, stats_class_data->time, maxTime,
(uint)V, stats_class_data->name, threadID, command_name[command], offsetLimit, *statVerify);
Shared->extsql_counter++;
extsql_thread_dump(thd, 1);
return;
}
stats_class_data->data[offset] += C;
offset = ARRAY_INDEX(i, var, maxVar, 0, maxTime);
stats_class_data->data[offset] += C;
break; // check the next class
} // end if - match
} // end for - all vars
} // end for - all classes
} // end function extsql_inc
#ifdef EXTSQL_BINARY
#include "../../../../../../../bin/checkParam.h"
#endif
// extsql_prep - invoked to set/confirm start type
// OUTPUT: startType - STATS_INIT_FAILURE, _NORMAL, _RESET, _RELOAD
// it will log an error message on _FAILURE
// INPUT: confFile - may be NULL (startType = NORMAL). If present the files is scanned
// for new global extsql vars: _class_list, _users, _reload_file, _debug, _license_key
// and the startType is set to _RESET.
// reload_file - may be NULL. If present and contents is valid, startType
// set to _RELOAD, file is scanned to make sure hostname and class list matches.
// NO global vars are changed!!
// NOTE: At present only ONE of confFile and realodFile may be set, error otherwise.
//
void extsql_prep(int *startType, const char *confFile, char *reloadFile) {
// if no extsql_reload_file is present:
// mysql: reloadFile == 0
// pgsql: reloadFile[0] == 0
extsql_init_globals();
if (! STATS_DEBUG(STATS_ADMIN) ) { // always normal
*startType = STATS_INIT_NORMAL;
return;
}
#ifdef PGSQL
if (!*startType && *reloadFile) *startType = STATS_INIT_RELOAD;
#else
if (!*startType && reloadFile) *startType = STATS_INIT_RELOAD;
#endif
if (*startType == STATS_INIT_RELOAD && reloadFile != NULL) {
// read reload file
File reloadHandle;
char *memPtr = NULL, *srcPtr = NULL, *tmpPtr = NULL, *tmpPtr2 = NULL;
int size, i, fieldLength;
char *rfVersion = NULL, *rfClassList = NULL, *rfHostname = NULL;
char *extsql_class_listNoWS; char hostname[FN_REFLEN];
if (gethostname(hostname, FN_REFLEN) < 0) {
strcpy(hostname, "localhost");
}
if ((reloadHandle = my_open(reloadFile, O_RDONLY|O_BINARY, MYF(0))) < 0 ) {
sql_print_information("ExtSQL: Specified reload_file could not be opened: %s", reloadFile);
*startType = STATS_INIT_FAILURE;
return;
}
if (!(size = extsql_read_section_bytes(reloadFile, reloadHandle, &memPtr,
"RELOAD", 0))) {
sql_print_error("ExtSQL: Error reading from file %s", reloadFile);
*startType = STATS_INIT_FAILURE;
return;
}
// find version, classlist and hostname
for (srcPtr = memPtr, i = 0; i < size; srcPtr++, i++) {
if (!strncmp(srcPtr, "host: ", 6)) { // found hostname
tmpPtr = srcPtr + strlen("host: ");
fieldLength = 0;
while (*tmpPtr != '\n') {
++fieldLength;
++tmpPtr;
}
rfHostname = strndup(srcPtr + strlen("host: "), fieldLength);
}
if (!strncmp(srcPtr, "EXT version: ", 17)) { // found extsql version
tmpPtr = srcPtr + strlen("EXT version: ");
fieldLength = 0;
while (*tmpPtr != '\n') {
++fieldLength;
++tmpPtr;
}
++fieldLength; // we want the trailing '\n' here
rfVersion = strndup(srcPtr + strlen("EXT version: "), fieldLength);
}
if (!strncmp(srcPtr, "extsql_class_list: ", 19)) { // found class list
tmpPtr = srcPtr + strlen("extsql_class_list: ");
fieldLength = 0;
while (*tmpPtr != '\n') {
++fieldLength;
++tmpPtr;
}
rfClassList = strndup(srcPtr + strlen("extsql_class_list: "),
fieldLength);
}
}
// by here, all pointers should be set, or we've got a badly
// formatted reload file
if (!(rfVersion && rfClassList && rfHostname)) {
sql_print_error("ExtSQL: Malformatted reload file %s", reloadFile);
*startType = STATS_INIT_FAILURE;
return;
}
// check for mismatches that are okay, but require confirmation
if (strcmp(rfHostname, hostname)) {
sql_print_information("ExtSQL: hostname mismatch (file: %s, server: %s)",
rfHostname, hostname);
*startType = STATS_INIT_CONFIRM;
return;
}
if (strcmp(rfVersion, Shared->extsql_version+1)) {
sql_print_information("ExtSQL: version mismatch (file: %s, server: %s)",
rfVersion, Shared->extsql_version+1);
*startType = STATS_INIT_CONFIRM;
return;
}
// we need a duplicate of extsql_class_list without whitespace
extsql_class_listNoWS = extsql_strdup(Shared->extsql_class_list, MYF(MY_WME));
for (tmpPtr = Shared->extsql_class_list, tmpPtr2 = extsql_class_listNoWS;
*tmpPtr != '\0'; ++tmpPtr) {
if (!(*tmpPtr == ' ' || *tmpPtr == '\t'))
*tmpPtr2++ = *tmpPtr;
}
*tmpPtr2 = '\0';
if (strcmp(rfClassList, extsql_class_listNoWS)) {
sql_print_information("ExtSQL: class list mismatch (file: %s, server: %s)",
rfClassList, extsql_class_listNoWS);
*startType = STATS_INIT_CONFIRM;
return;
}
my_free(extsql_class_listNoWS, MYF(0));
my_free(rfHostname, MYF(0));
my_free(rfVersion, MYF(0));
my_free(rfClassList, MYF(0));
my_free(memPtr, MYF(0));
} else if (*startType == STATS_INIT_RESET && confFile != NULL) {
char line[2048];
FILE* conf = fopen(confFile, "r");
// parse conf file
if (conf == NULL) {
sql_print_error("ExtSQL: Unable to open conf file %s", confFile);
*startType = STATS_INIT_FAILURE;
return;
}
while (fgets(line, 1024, conf) != NULL) {
// lowercase directive name and strip all whitespace
bool foundEqual = false;
int equalPos = 0, readPos = 0, writePos = 0;
char *directive, *data;
while (line[readPos] != '\0') {
if (line[readPos] == '=') {
foundEqual = true;
equalPos = writePos;
}
if (!foundEqual && line[readPos] > 64 && line[readPos] < 91)
line[readPos] += 32;
if (!(line[readPos] == '\t' || line[readPos] == ' ' ||
line[readPos] == '\n' || line[readPos] == '"' ||
line[readPos] == '\'')) {
line[writePos] = line[readPos];
++writePos;
}
++readPos;
} // end while line
line[writePos] = '\0';
if (strncmp(line, "extsql_", 7)) continue;
directive = strndup(line, equalPos);
data = line + (equalPos + 1) * (sizeof(char));
// set the appropriate data member
if (!strcmp(directive, "extsql_class_list"))
Shared->extsql_class_list = extsql_strdup(data, MYF(MY_WME));
else if (!strcmp(directive, "extsql_users"))
extsql_users = extsql_strdup(data, MYF(MY_WME));
else if (!strcmp(directive, "extsql_reload_file"))
Shared->extsql_reload_file = extsql_strdup(data, MYF(MY_WME));
else if (!strcmp(directive, "extsql_debug"))
Shared->extsql_debug = atoi(extsql_strdup(data, MYF(MY_WME)));
else if (!strcmp(directive, "extsql_key")) {
extsql_key = extsql_strdup(data, MYF(MY_WME));
sql_print_information("ExtSQL: extsql_key %s", data);
}
else {
sql_print_error("ExtSQL: Unknown configuration directive: %s",
directive);
*startType = STATS_INIT_FAILURE;
}
} // end while read conf
} else {
*startType = STATS_INIT_NORMAL;
}
}
// extsql_init_globals - MySQL uses threads for each server, Postgres uses a separate process.
// Shared memory is used to speed our operations, data seg is shared between threads in MySQL,
// but separate in postgres. To keep common code we need to alloc the area for our globals,
// store in section //GLOBALS a top of this file. Not really needed to MySQL, but doesn't hurt.
// BIG code change, need to switch all refs to pointers!
// my_malloc is ifdef'd for PGSQL to do the shared memory magic!
// Return 1 on failure
int extsql_init_globals() {
#ifdef PGSQL
// we allocate a big hunk of shared memory
bool memFound = false;
ShmemPtr = (char *) ShmemInitStruct("ExtSQL Data", SHMEM_SIZE, &memFound);
if (!ShmemPtr) {
sql_print_error("ExtSQL extsql_init_globals: can't init shared mem area of %d bytes", SHMEM_SIZE);
return(1);
}
if STATS_DEBUG(STATS_DUMP_START) {
sql_print_information("ExtSQL extsql_init_globals: ShmemPtr at 0x%x, memFound(%d)", (uint)ShmemPtr, memFound);
}
if (memFound) { // issue warning, should have only been called once!
// set our two pointers again
MemLock = (slock_t *) ShmemPtr; // already initialized.
NextFree = (char **) ShmemPtr + sizeof(slock_t *);
return(0); // ZZ - not fatal?
}
// if we get here, FIRST and ONLY time through for original allocation.
// our MemLock and NextFree live at the start of the region.
// this is needed for the PGSQL version of extsql_malloc
MemLock = (slock_t *) ShmemPtr;
SpinLockInit(MemLock);
NextFree = (char **)ShmemPtr + sizeof(slock_t *);
*NextFree = (char *)NextFree + sizeof(NextFree);
// okay alloc the global space
Shared = (pGLOBAL) extsql_malloc(sizeof(GLOBAL), MYF(MY_WME | MY_ZEROFILL) );
if (!Shared) {
sql_print_error("ExtSQL extsql_init_globals: can't alloc shared area.");
return(1);
}
Shared->extsql_class_list = extsql_strdup(extsql_class_list, MYF(MY_WME));
Shared->extsql_key = extsql_strdup(extsql_key, MYF(MY_WME));
Shared->extsql_users = extsql_strdup(extsql_users, MYF(MY_WME));
Shared->extsql_version = extsql_strdup(extsql_version, MYF(MY_WME));
Shared->extsql_reload_file = extsql_strdup(extsql_reload_file, MYF(MY_WME));
#else
static int didInit = 0;
if (!didInit) {
// THIS IS MYSQL, the Shared structure is static and already declared.
Shared->extsql_class_list = extsql_class_list;
Shared->extsql_key = extsql_key;
Shared->extsql_users = extsql_users;
Shared->extsql_version = extsql_version;
Shared->extsql_reload_file = extsql_reload_file;
didInit = 1;
} else {
return(0);
}
(void) pthread_mutex_init(&Shared->LOCK_stats_clock,MY_MUTEX_INIT_FAST);
#endif
// take care of copying some startup global values
Shared->extsql_active = extsql_active;
Shared->extsql_counter = extsql_counter;
Shared->extsql_debug = extsql_debug;
if STATS_DEBUG(STATS_DUMP_START) {
sql_print_information("ExtSQL: Globals are active(%d), debug(%d), extsql_class_list(%s), extsql_version(%s), extsql_users(%s), extsql_reload_file(%s)",
(int)Shared->extsql_active, (int)Shared->extsql_debug, Shared->extsql_class_list,
Shared->extsql_version, Shared->extsql_users, Shared->extsql_reload_file);
}
return(0);
} // end extsql_init_globals
// Called to initialize tracking data structures, potentially reload with prior data.
// We may be here from a NORMAL init, first time, or a RESET in the middle of a server run
// or a RELOAD from a start or the middle of server run.
// extsql_prep has already been invoked, so global extsql_ vars are set
int extsql_init(char *extsql_classlist, int startType, char *reloadFile)
{
int extsql_willbe_active = 1; // track state during init process, global extsql_active is
// always set to off during init.
int reloadFail = 0;
//extsql_class_list is a comma separated list of which define what we will
// be tracking, number of instances, times of data, and which variables
// "db,max-5,time-10,units-d(Com_show_tables,Com_show_variables),
// user,max-50,time-24,units-m(Bytes_sent,Bytes_received)"
// class_name, max-instance_limit, max-time_limit, units-(min|day|hour) (var_list)
char *parse_string, *malloc_addr;
char *token, *tmpPtr;
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **var_ptr = 0;
struct show_var_st *status_var_ptr = status_vars;
int found = 0, skip = 0, foundParen = 0, num = 0, i = 0, j = 0, size = 0, classCount = 0, totalSize = 0;
int thisDebug = 0; char timeUnit;
sql_print_information("ExtSQL start type(%s): %s", startTypeName[startType], Shared->extsql_version+1);
// allocate our global area
if (extsql_init_globals()) {
sql_print_error("ExtSQL: start aborted. Failed to alloc global shared mem.");
return(1);
}
// Shared->extsql_class_list = my_strdup(extsql_class_list, MYF(MY_WME));
// Shared->extsql_reload_file = my_strdup(extsql_reload_file, MYF(MY_WME));
Shared->extsql_active = 0; // will be set at end of this function if returned to active
if (startType == STATS_INIT_FAILURE) {
sql_print_error("ExtSQL: disabled. Start aborted due to prior errors");
// We do not set Shared->extsql_disabled, may just be a failed reload/reset, okay to try again.
return(1);
}
#ifdef EXTSQL_BINARY
int status, days;
// license sequence is ONLY area for returns before end of function!
if (!extsql_key) {
sql_print_error("ExtSQL: disabled. No license key found for binary build, make sure to define ExtSQL_key in /etc/my.cnf.");
Shared->extsql_active = 0; // turn it all off
Shared->extsql_disabled = 1;
return(1);
}
if (status = checkParam(Shared->extsql_version+1, extsql_key, &days)) {
sql_print_error("ExtSQL: disabled. Improper license key found(%s), code %d.",
Shared->extsql_version+1, status);
Shared->extsql_active = 0; // turn it all off
Shared->extsql_disabled = 1; // turn it all off
return(1);
}
if (days == 99999) {
sql_print_information("ExtSQL: Permanent license verified.");
} else if (days < 31) {
// limited time
sql_print_warning("ExtSQL: Evaluation license good for server startup only %d more days.", days);
} else {
// expired
sql_print_error("ExtSQL: disabled. Evaluation period has expired.");
Shared->extsql_disabled = 1; // turn it all off
Shared->extsql_active = 0; // turn it all off
return(1);
}
#endif
if (!extsql_users) {
sql_print_information("ExtSQL: no defined user list, only root user allowed");
extsql_users="root";
} else {
sql_print_information("ExtSQL: access allowed only to users: %s", extsql_users);
}
if (!extsql_class_list) {
extsql_willbe_active = 0; // turn it all off
Shared->extsql_disabled = 1; // turn it all off
Shared->extsql_active = 0; // turn it all off
sql_print_information("ExtSQL: deactivated by user, no extsql_class_list defined");
return(1);
} else {
extsql_willbe_active = 1;
}
//init our var addr tracking array to unstored
bzero((char *) Shared->varAddrTrackArray, sizeof(Shared->varAddrTrackArray));
Shared->varAddrCount = 0;
// if we are here because of a RESET, there should be something in the statAllocArray
// from prior - check to make sure -- if _NORMAL, we zero the whole thing.
if (startType != STATS_INIT_NORMAL) { // _RELOAD or _RESET
// wait just a bit for all threads to know we've stopped
Shared->extsql_reload_in_progress = 1;
sleep(2);
if (startType == STATS_INIT_RESET && ( ! Shared->statsAllocArray[0] || ! Shared->allocIndex) ) { // empty!
sql_print_error("ExtSQL: deactivated, RESET request failed, no prior data found.");
extsql_willbe_active = 0;
} else { // free up any existing for RELOAD or RESET
char *addr;
for (i = 0, addr = Shared->statsAllocArray[i]; (addr = Shared->statsAllocArray[i]) && i < STATS_MAX_NUM_ALLOC; i++) {
my_free(addr, MYF(0));
}
} // end if - memory clear necessary
} else {
Shared->extsql_reload_in_progress = 0;
} // end if-else startType other than NORMAL
bzero((char *) Shared->statsAllocArray, sizeof(Shared->statsAllocArray));
Shared->allocIndex = 0; // for recording mem allocations
bzero((char *) &Shared->statData, sizeof(Shared->statData)); // zero out all the contents, support restart - reload
if STATS_DEBUG(STATS_DUMP_START) sql_print_information("ExtSQL tracking classes: %s", extsql_class_list); //DEBUG
if (!(parse_string = extsql_strdup(extsql_class_list, MYF(MY_WME)))) {
Shared->extsql_active = 0;
Shared->extsql_disabled = 1;
Shared->extsql_reload_in_progress = 0;
sql_print_error( "ExtSQL: disabled, unable to dup extsql_class_list(%s)",
extsql_class_list);
return(1);
}
if (startType == STATS_INIT_NORMAL || startType == STATS_INIT_RESET) { // init structures on RESET/NORMAL starts, skip on RELOAD
int i = 0; // track our position in statVars
Shared->statsAllocArray[Shared->allocIndex++] = parse_string;
malloc_addr = parse_string;
while ( (token = strtok(parse_string, ", ")) ) { // start new loop on each class name
char **tPtr;
parse_string = NULL;
found = 0; num = 0;
// should have a class name
if (thisDebug) sql_print_information("ExtSQL, Class token is (%s)", token);
// is it allowable
for (tPtr = extsqlClassesSupported; *tPtr; tPtr++) {
if (!strcmp(token, *tPtr)) {
found = 1;
break;
}
} // end for - allowable class names
if (! found) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, bad class name (%s)", token);
break;
}
// we have a valid class name, make sure it is not a dup
found = 0;
for (stats_class_data = Shared->statData; stats_class_data->name[0]; stats_class_data++) {
if (!strcmp(token, stats_class_data->name)) {
found = 1;
break;
}
} // end for - existing classes
if (found) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, duplicate class name (%s)", token);
break;
}
// okay we have a good name, start loading the structure
strncpy(stats_class_data->name, token, STATS_MAX_NAME-1);
// set our starting time
stats_class_data->time = 0;
// get the next token, should be instance limit
if (! (token = strtok(parse_string, ", "))) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, incomplete class definitions, started with (%s)",
extsql_class_list);
break;
}
// should start with max-
if (strncmp(token, "max-", 4)) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing max instance amount, got (%s)",
token);
break;
}
// get the number for max instances and allocate space
num = atoi(token+4);
if (!num) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing valid instance amount, got (%s)",
token);
break;
}
stats_class_data->maxInstance = num;
// we null term it by getting one more than required
if (!(stats_class_data->instanceIndex = (char **)extsql_malloc( ((num+1) * sizeof(char *)),
MYF(MY_WME | MY_ZEROFILL)) )) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, can't allocate class instance memory");
break;
}
Shared->statsAllocArray[Shared->allocIndex++] = (char *)stats_class_data->instanceIndex;
// good instance limit, now get time limit
if (! (token = strtok(parse_string, ", "))) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, incomplete class definitions, missing time limit, started with (%s)",
extsql_class_list);
break;
}
// should start with time-
if (strncmp(token, "time-", 5)) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing max time amount, got (%s)",
token);
break;
}
// get the number for max times
num = atoi(token+5);
num += 1; // always the zero hour - totals
if (!num || num < 2) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing valid time amount, got (%s)",
token);
break;
}
stats_class_data->maxTime = num;
// allocate space ZZ add to size count reported
if (!(stats_class_data->timeLabels = (my_time_t *)extsql_malloc( ((num+1) * sizeof(my_time_t)),
MYF(MY_WME | MY_ZEROFILL)) )) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, can't allocate class time label memory");
break;
}
Shared->statsAllocArray[Shared->allocIndex++] = (char *) stats_class_data->timeLabels;
// get the time Units, m - minutes, h - hours, d - days
if (! (token = strtok(parse_string, ", "))) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, incomplete class definitions, missing timeUnits started with (%s)",
token);
break;
}
// should start with units-
if (strncmp(token, "units-", 6)) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing units amount, got (%s)",
token);
break;
}
// get the time units
timeUnit = *(token+6);
if (timeUnit == 'm') {
stats_class_data->timeUnits = 'm';
} else if (timeUnit == 'h') {
stats_class_data->timeUnits = 'h';
} else if (timeUnit == 'd') {
stats_class_data->timeUnits = 'd';
} else {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, missing valid time unit amount, got (%c)",
timeUnit);
break;
}
// set remaining vars
stats_class_data->maxVar = 0;
stats_class_data->lastTime = 0;
// okay, next token is var list like: (Com_show_tables,Com_show_variables) OR
// ( Com_.... )
// first and last token have parens
// clear out tmp storage
j = 0; // keep track of var tokens
i = 0;
foundParen = 0; // haven't seen closing paren
while (!foundParen && (token = strtok(parse_string, ", ")) ) {
if (thisDebug) sql_print_information("ExtSQL, variable token is (%s)", token);
// none of the tokens should be close to MAX_VAR_SIZE
if (strlen(token) > MAX_VAR_SIZE - 5) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL, var name too long in var list (%s)",
token);
break;
}
// if we got a closing paren by itself, we're done
if (!strcmp(")", token)) {
break;
}
// should have a paren( on first one, move past that
if (!j) {
if (token[0] != '(') {
extsql_willbe_active = 0;
sql_print_error("ExtSQL, bad format in var list, missing ( in %s",
token);
break;
}
// okay, now we count the , from opening to closing paren, need to know
// how many vars there are so we can allocate space for pointer array to them
for (i = 0, num = 1, tmpPtr = token; *tmpPtr != ')'; tmpPtr++) {
if (*tmpPtr == ',') {
num++;
}
} // end for - token string
// we null term pointer array by alloc of one extra
if (!(stats_class_data->varIndex = (STAT_VAR **)extsql_malloc( ((num+1) * sizeof(pSTAT_VAR)),
MYF(MY_WME | MY_ZEROFILL)) )) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, can't allocate class varIndex memory");
break;
}
Shared->statsAllocArray[Shared->allocIndex++] = (char *) stats_class_data->varIndex;
var_ptr = stats_class_data->varIndex; // pointer to array of pointer to each var we find below
// check to see if got just a ')', in that case we get the next
if (!strcmp(token, "(")) {
token = strtok(parse_string, ", ");
} else {
token++; // move off paren
}
if (thisDebug) sql_print_information("ExtSQL, FIRST variable token is (%s)", token);
} // end if - starting paren
if (token[strlen(token) - 1] == ')') { // last one
token[strlen(token) - 1] = '\0'; // null it out early
foundParen = 1;
if (thisDebug) sql_print_information("ExtSQL, LAST variable token is (%s)", token);
}
// unless the debug flag is set, check to see if allowed
if (!STATS_DEBUG(STATS_EXTRA_VARS)) {
char **varPtr; extsql_willbe_active = 0;
for (varPtr = allowedVars; *varPtr; varPtr++) {
if (!strcmp(*varPtr, token)) {
extsql_willbe_active = 1;
break;
} // end if
} // end for - all allowed
if (!extsql_willbe_active) {
sql_print_error("ExtSQL, requested tracking var (%s) not on approved list. Please check spelling and documenation. This setting can be overridden via debug flag (STATS_EXTRA_VARS)",
token);
break;
}
} // end if - check approved vars
j++; // token count
// we should now have a token, look for match in status_vars
found = 0; skip = 0;
for (status_var_ptr = status_vars; status_var_ptr->name; status_var_ptr++) {
if (thisDebug) sql_print_information("ExtSQL, status_var is %s(0x%x)",
status_var_ptr->name, (uint)status_var_ptr->value );
if (!strcmp(token, status_var_ptr->name)) { // match
if (!status_var_ptr->value && strcmp(token, "Questions")) { // no address assigned, not tracking this
// okay if Questions, we will fix below
#ifdef EXTSQL_50
// okay if Bytes_received - first item
if (strcmp(token,"Bytes_received")) { // only okay for Bytes_received
#endif
sql_print_error("ExtSQL, variable (%s) has no address, can not be tracked",
status_var_ptr->name);
extsql_willbe_active = 0;
break;
#ifdef EXTSQL_50
}
#endif
} // end if - no address
// allocate room for the structure itself, put the address in the pointer array
if (!(*var_ptr = (STAT_VAR *)extsql_malloc( (sizeof(STAT_VAR)),
MYF(MY_WME | MY_ZEROFILL)) )) {
extsql_willbe_active = 0;
skip = 1;
sql_print_error("ExtSQL: deactivated, can't allocate class var memory");
break;
}
Shared->statsAllocArray[Shared->allocIndex++] = (char *) *var_ptr;
if (!((*var_ptr)->name = extsql_strdup(status_var_ptr->name, MYF(MY_WME)))) {
extsql_willbe_active = 0;
sql_print_error( "ExtSQL: deactivated, unable to alloc for (%s)",
status_var_ptr->name);
break;
} // end if - malloc
Shared->statsAllocArray[Shared->allocIndex++] = (*var_ptr)->name;
#ifdef PGSQL
(*var_ptr)->addr = (char *)status_var_ptr->value;
#else
// SPECIAL here, if the value is Connections or Questions, we override with our internal
// tracking variable address (connects or queries) -- special case for them.
if (!strcmp(token, "Questions")) {
(*var_ptr)->addr = (char *)&Shared->queries;
} else if (!strcmp(token, "Connections")) {
(*var_ptr)->addr = (char *)&Shared->connects;
} else { // regular
(*var_ptr)->addr = (char *)status_var_ptr->value;
}
#endif
found = 1;
if (thisDebug) sql_print_information("ExtSQL: tracking varIndex[%d] var(%s), addr(0x%x)\n",
i, (*var_ptr)->name, (uint)(*var_ptr)->addr);
(uint)stats_class_data->maxVar++;
i++;
// store the addr in our quick look up array, only if its not already there
if (extsql_store_var_addr((*var_ptr)->addr)) {
extsql_willbe_active = 0;
break;
}
break ; // look for next one
} // end if - match
} // end for - more status vars
if (skip) { // we missed a variable that had a zero address, that is okay.
continue;
}
if (! found ) { // could not complete match on one of their variables
extsql_willbe_active = 0;
sql_print_error( "ExtSQL: deactivated, could not complete variable match for (%s)",
token);
}
if (! extsql_willbe_active || ! stats_class_data->maxVar) { // had failure somewhere above
extsql_willbe_active = 0;
break;
}
var_ptr++;
} // end while - more tokens in var list
if (! extsql_willbe_active) { // had a failure above
break;
}
} // end while - more token in class definitions
} else if (startType == STATS_INIT_RELOAD) {
// make copy of class list in the event of failure
char* oldClassList = extsql_strdup(extsql_class_list, MYF(MY_WME));
if (extsql_reload(STATS_READ_RELOAD, reloadFile)) { // reload failed
sql_print_error("ExtSQL: Reload FAIL--resetting class list OLD(%s), NEW(%s)",
oldClassList, extsql_class_list);
// restore old class list
extsql_class_list = oldClassList;
// reset
startType = STATS_INIT_RESET;
extsql_willbe_active = 1; // reload should log error
reloadFail = 1; // flag to return error status
}
} // end if-else startType (NORMAL, RESET or RELOAD
if (extsql_willbe_active) { // calculate data sizes for each class and print what we got
size = 0;
for (stats_class_data = Shared->statData, classCount = 0;
classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
if STATS_DEBUG(STATS_DUMP_START) sql_print_information("ExtSQL, class %s, max inst %d, max vars %d, max time %d %d",
stats_class_data->name, stats_class_data->maxInstance,
stats_class_data->maxVar, stats_class_data->maxTime,
stats_class_data->timeUnits);
// loop through all vars
for (var_ptr = stats_class_data->varIndex, i = 0; i < stats_class_data->maxVar;
var_ptr++, i++) {
if STATS_DEBUG(STATS_DUMP_START) sql_print_information(" Var %s at addr 0x%x", (*var_ptr)->name, (uint)(*var_ptr)->addr);
} // end for all vars defined
size = stats_class_data->maxInstance * stats_class_data->maxVar *
stats_class_data->maxTime * sizeof(ulong);
// if NOT RELOAD (a NORMAL OR RESET start) allocate data space
if (startType != STATS_INIT_RELOAD) {
if (!(stats_class_data->data = (ulong *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)) )) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, failed to alloc %d bytes for class %s data",
size, stats_class_data->name);
break;
}
Shared->statsAllocArray[Shared->allocIndex++] = (char *)stats_class_data->data;
stats_class_data->dataEnd = stats_class_data->data + size/sizeof(ulong);
}
// set end marker
if STATS_DEBUG(STATS_DUMP_START) sql_print_information(" Data allocated %d bytes, data start at 0x%x, end at 0x%x",
size, (uint)stats_class_data->data, (uint)stats_class_data->dataEnd);
totalSize += size;
#ifdef PGSQL
if (totalSize + SHMEM_RESERVE > SHMEM_SIZE) {
extsql_willbe_active = 0;
sql_print_error("ExtSQL: deactivated, projected mem usage (%d) too close to limit of %d",
size, SHMEM_SIZE);
}
#endif
} // end for - all classes
} // end if - stats will be active
if (startType != STATS_INIT_NORMAL) { // RELOAD or RESET, clear any stored thread instance info
#ifndef PGSQL
// loop through ALL threads and zero indexes
i = 1;
while (pthread_mutex_trylock(&LOCK_thread_count)) { // didn't get it, do UNLOCK!!!!
sleep(i++);
sql_print_information("ExtSQL: Trying to completed RELOAD/RESET, can't get lock, sleeping for (%d)",i);
if (i > 5) {
extsql_willbe_active = 0;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
break;
}
} // end while - get thread lock
if (extsql_willbe_active) { // loop through all threads
I_List_iterator it(threads);
THD *tmp;
int *indexPtr, *statVerify;
while ((tmp=it++)) {
extsql_thread_init(tmp, STATS_THD_INIT_ALL);
} // end while - more threads
} // end if - extsqlwillbeactive
VOID(pthread_mutex_unlock(&LOCK_thread_count));
#endif
#ifdef NEVER__
}
#endif
} // end if - RELOAD or RESET
if (extsql_willbe_active) {
Shared->extsql_active = 1;
sql_print_information("ExtSQL ACTIVE allocated memory: %d bytes for %d classes",
totalSize, classCount);
} else {
sql_print_information("ExtSQL NOT ACTIVE");
Shared->extsql_active = 0;
}// end if - stats active
Shared->extsql_reload_in_progress = 0; // in all cases.
if (Shared->extsql_active && !reloadFail) {
return(0);
} else {
return(1);
}
} // end extsql_init
// extsql_init_proc_vars - is used to abstract the different locations in which both
// MySQL and Postgres store process/status info
// this is always with respect to the current thread -- not all are available for both!
static void extsql_init_proc_vars(int *threadID, int *command, char **user, char **db, char **host,
char **proc_info, int **indexPtr, int **statVerify) {
#ifdef PGSQL
THD *thd = MyProc;
#else
THD *thd = current_thd;
#endif
#ifdef PGSQL
*user = MyProcPort->user_name;
*statVerify = &thd->statVerify;
*indexPtr = thd->statIndex;
*proc_info = "n/a";
*host = "n/a";
*command = 1;
*threadID = getpid();
#else
#ifdef EXTSQL_50
*user = thd->security_ctx->user;
*host = thd->security_ctx->host;
*statVerify = &thd->security_ctx->statVerify;
*indexPtr = thd->security_ctx->statIndex;
#else
*user = thd->user;
*host = thd->host;
*statVerify = &thd->statVerify;
*indexPtr = thd->statIndex;
#endif
// more just MySQL
*command = thd->command;
*proc_info = (char *)thd->proc_info;
*threadID = thd->thread_id;
#endif
#ifdef PGSQL
*db = MyProcPort->database_name;
#else
*db = thd->db;
#endif
} // end extsql_init_proc_vars
// this function prints hex bytes from the startAddr for size
static void extsql_print_bytes(char *startAddr, int size) {
char *tmpPtr;
int i;
char buff[120] = {'\0'};
for (i = 0, tmpPtr=startAddr; i < size; tmpPtr++, i++) {
if (!(i%8) && strlen(buff)) {
sql_print_information("ExtSQL: 0x%x - %s", (uint)startAddr+i, buff);
buff[0] = '\0';
}
sprintf(buff+strlen(buff), "0x%x (0x%x) / ", i, *tmpPtr);
} // end for - entire string
if (strlen(buff)) {
sql_print_information("ExtSQL: 0x%x - %s", (uint)startAddr+i, buff);
}
} // end extsql_print_bytes
// this utility function reds a section header in a
// RELOAD file and stores to address of memPtr,
// as input if memPtr is zero, this function allocates memory and it must be
// freed by caller. If memPtr is nonzero, we assume caller has allocated space!
// returns 0 on error, hdr is the name of the header we should be reading
// num is context for an error.
int extsql_read_section_bytes(char *fileName, File fileDes, char **memPtr,
char *hdr, int num) {
char buff[80];
int size;
// read the section hdr
if (my_read(fileDes, buff, STATS_SECTION_HDR, MYF(MY_WME)) != STATS_SECTION_HDR) {
sql_print_error("ExtSQL: Section header byte count read failed on file %s, (%s/%d)",
fileName, hdr, num);
return(0);
}
if (!strstr(buff, hdr)) { // missing
sql_print_error("ExtSQL: Section header from file %s unexpected, got %s, wanted %s",
fileName, buff, hdr);
return(0);
}
size = atoi(buff + STATS_SECTION_BYTES);
if (size <= 0) {
sql_print_error("ExtSQL: Section header bad size (%d) on file %s, (%s/%d)",
size, fileName, hdr, num);
return(0);
}
if (*memPtr == NULL) { // alloc memory for the data
if (!(*memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
sql_print_error("ExtSQL: Could not alloc %d bytes to read data on file %s, (%s/%d)",
size, fileName, hdr, num);
return(0);
}
}
if (my_read(fileDes, *memPtr, size, MYF(MY_WME)) != size) {
sql_print_error("ExtSQL: Section header data read failed on file %s for %d bytes, (%s/%d)",
fileName, size, hdr, num);
return(0);
}
return(size);
} // end extsql_read_section_bytes
// extsql_write_section_hdr - writes the header for a section to the RELOAD
// file. The hdr should curretnly be 6 chars in length. The size is the
// size of the data to be stored in that section.
// Returns non zero on error, puts msg in error log.
static int extsql_write_section_hdr(char *loadFileName, File reloadFile, char *hdr, int size) {
char buff[60];
if (strlen(hdr) != 6) {
sql_print_error("ExtSQL: Bad header length for %s", hdr);
return(1);
}
sprintf(buff, "\nEXT %s: %09d\n", hdr, size);
if (strlen(buff) != STATS_SECTION_HDR) { // sanity check
sql_print_error("ExtSQL: Bad length for %s section hdr, %d/%d",
hdr, STATS_SECTION_HDR, strlen(buff));
return(1);
}
if (my_write(reloadFile, (byte*) buff, STATS_SECTION_HDR, MYF(MY_WME)) != STATS_SECTION_HDR) {
sql_print_error("ExtSQL: Could not write %s header section to RELOAD file %s (%d/%d)",
hdr, loadFileName, size, reloadFile);
return(1);
}
return(0);
} // end - extsql_write_section_hdr
// manage reload data - this function take care of reading/write reload data.
// WRITE_RELOAD: It creates the file used for the RELOAD command, most of it
// consists of binary data, but the first few lines are in ASCII so that a 'head' can
// be done on the file to extract general info
// READ_RELOAD: Reads in file, sets globa statData and extsql_class_list to what is found
// in the file.
// IN: action (STATS_WRITE_RELOAD, STATS_READ_RELOAD)
// IN: loadFileName (name of reload file to read/write)
// GLOBALS: will read/write extsql_class_list and statData
int extsql_reload(int action, char *loadFileName) {
File reloadFile; // a file descriptor
char buff[STATS_TMP_BUFF_SIZE];
char *memPtr; // memPtr holds dynamic alloc, needs to be freed!
my_time_t now_sec;
MYSQL_TIME mysql_time;
#ifndef PGSQL
THD *thd = _current_thd();
#endif
char dateBuff[80];
#define CLASS_SIZE 8192
char classBuff[CLASS_SIZE];
char *srcPtr, *destPtr, *actionStr, *className, **ptrPtr;
pSTATS_CLASS_DATA stats_class_data;
int maxVar, maxTime, maxInstance, var, found, instance, classCount, size, size2, mode, i;
int numClasses;
char hostname[FN_REFLEN]; // defined in my_global.h
if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
sql_print_information(adminDisabled);
return(0);
}
if (gethostname(hostname, FN_REFLEN) < 0) { // failed
strcpy(hostname, "localhost");
}
// Shared->extsql_debug |= STATS_RELOAD;
if (action == STATS_WRITE_RELOAD) { // set some params
mode = O_WRONLY|O_CREAT|O_BINARY;
actionStr = "write/store";
} else if (action == STATS_READ_RELOAD) {
mode = O_RDONLY|O_BINARY;
actionStr = "read/restore";
} else {
sql_print_error("ExtSQL: unexpected reload request (%d)", action);
return(1);
}
if (strlen(Shared->extsql_class_list) >= CLASS_SIZE) {
sql_print_error("ExtSQL: extsql_class_list too long for configured limit, can't save RELOAD data.");
return(1);
}
// open the file
if ((reloadFile= my_open(loadFileName, mode, MYF(0))) < 0 ) {
sql_print_error("ExtSQL: Unable to open %s for %s of RELOAD data, aborted (%d).",
loadFileName, actionStr, errno);
return(1);
}
sql_print_information("ExtSQL: (%s) Reload file is %s, Current active extsql_class_list is %s",
actionStr, loadFileName, Shared->extsql_class_list);
// get the current date/time, do any conversions
now_sec = time(NULL);
#ifdef PGSQL
// need time convesion ZZ
mysql_time = now_sec;
#else
thd->variables.time_zone->gmt_sec_to_TIME(&mysql_time, (my_time_t) now_sec);
#endif
my_datetime_to_str(&mysql_time, dateBuff);
// strip ALL blanks from current class_list, count classes
numClasses = 0;
for (srcPtr=Shared->extsql_class_list, destPtr=classBuff; *srcPtr; srcPtr++) {
if (*srcPtr != ' ' && *srcPtr != '\t') {
*destPtr++ = *srcPtr;
}
if (*srcPtr == ')') {
numClasses++;
}
} // end for - all chars
// null term
*destPtr = '\0';
// write/read the header lines first, following format, designed to allow grep "EXT" to show
// useful info:
//
// line 0 - "\nEXT RELOAD: 000000025\n (ALL section headers, 12 char prefix, 9 char size
// line 1 - "EXT data stored on: date/time\n"
// line 2 - "EXT From host: bit.bongo.com\n"
// line 3 - "EXT extsql version: version\n"
// line 4 - "EXT\n"
// line 3 - "EXT ExtSQL extsql_class_list: db,max-130,time-100,units-m,(Binlog_cache_disk_use,Binlog_cache_use...\n"
// line 5 - "EXT\n";
if (action == STATS_WRITE_RELOAD) {
sprintf(buff, "\
EXT data stored: %s\n\
EXT from host: %s\n\
EXT version: %s\n\
EXT num classes: %d\n\
EXT\n\
EXT extsql_class_list: %s\n\
EXT\n", dateBuff, hostname, Shared->extsql_version+1, numClasses, classBuff);
if (extsql_write_section_hdr(loadFileName, reloadFile, "RELOAD", strlen(buff))) {
return(1);
}
if (my_write(reloadFile, (byte*) buff, strlen(buff), MYF(MY_WME)) != strlen(buff)) {
sql_print_error("ExtSQL: Could not %s header data to RELOAD file %s", actionStr, loadFileName);
return(1);
}
} else { // READ
char *classListStart;
memPtr = NULL; // read_section_bytes will alloc space
if (!(size = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
"RELOAD", 0))) {
return(1);
}
// find the stored class list.
found = 0;
for (srcPtr = memPtr, i = 0; i < size; srcPtr++, i++) {
if (!strncmp(srcPtr, "extsql_class_list:", 18)) { // found it
found = 1;
classListStart = srcPtr + strlen("extsql_class_list: ");
break;
}
} // end for - all chars in header
if (!found) {
sql_print_error("ExtSQL: Could not find class info in %s, section size (%d) (%s)",
loadFileName, size, memPtr);
my_free(memPtr, MYF(0));
return(1);
}
// NULL term the class list
for (i = 0; *(srcPtr+i) != '\n'; i++) ;
*(srcPtr+i) = '\0'; // null term
// check for new class list
if (strncmp(classListStart, classBuff, strlen(classBuff)) ||
strlen(classListStart) != strlen(classBuff)) {
char *numClassPtr;
if (!(Shared->extsql_class_list = extsql_strdup(srcPtr, MYF(MY_WME)))) {
sql_print_error("ExtSQL: reload failed, unable to alloc space for new class list");
return(1);
}
// check the number of classes present
numClasses = 0;
if (!(numClassPtr = strstr(memPtr, "num classes: "))) {
sql_print_error("ExtSQL: failed to find class count in reload file");
return(1);
}
numClasses = atoi(numClassPtr + strlen("num classes: "));
sql_print_information("ExtSQL: %d classes found, new class list will be loaded: %s",
numClasses, Shared->extsql_class_list);
} // end if - new class list
my_free(memPtr, MYF(0));
} // end if - else (first section)
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: during %s on %s, completed section RELOAD", actionStr, hostname);
}
// loop and read/write each class data to/from the RELOAD file
classCount = 0;
for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && classCount < numClasses;
stats_class_data++, classCount++) {
STAT_VAR **statPtr;
// -- below section repeated for each class
// -- write the binary data in sections, each unique section preceded by byte count that follows
// line 6 - "\nEXT CLAS n: 000000000\n" (class number, 0..n
// - structure STATS_CLASS_DATA
sprintf(buff, "CLAS %1d", classCount);
if (action == STATS_WRITE_RELOAD) {
if (extsql_write_section_hdr(loadFileName, reloadFile, buff, sizeof(STATS_CLASS_DATA))) {
return(1);
}
if (my_write(reloadFile, (byte*) stats_class_data, sizeof(STATS_CLASS_DATA),
MYF(MY_WME)) != sizeof(STATS_CLASS_DATA)) {
sql_print_error("ExtSQL: Could not %s class %d header data to RELOAD file %s", actionStr,
classCount, loadFileName);
return(1);
}
} else { // READ
memPtr = (char *) stats_class_data;
if (!extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
buff, classCount)) {
return(1);
}
}
className = stats_class_data->name;
maxVar = stats_class_data->maxVar;
maxTime = stats_class_data->maxTime;
maxInstance = stats_class_data->maxInstance;
// sanity check, make sure the className is on our supported list
for (i = 0, ptrPtr=extsqlClassesSupported; i < STATS_MAX_CLASSES; i++, ptrPtr++) {
found = 0;
if (! strcmp(className, *ptrPtr)) {
found = 1;
break;
}
} // end for - all supported classes
if (!found) {
sql_print_error("ExtSQL: Bad class name found (%s)", className);
return(1);
}
// print out summary info on the CLASS
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: During reload (%s), starting section %s", actionStr, buff);
sql_print_information("CLASS is %s, maxInstance(%d), maxVar(%d), maxTime(%d), time(%d), \
timeUnits(%c), lastTime(%d)",
stats_class_data->name, maxInstance, maxVar, maxTime,
stats_class_data->time, stats_class_data->timeUnits,
stats_class_data->lastTime);
}
// - "\nEXT INSTNC: 3400\n"
// - instanceNames 1..n from char **instanceIndex
if (action == STATS_WRITE_RELOAD) {
// count the size of what we have
size = 0;
for (instance = 0; instance < maxInstance && stats_class_data->instanceIndex[instance];
instance++) {
size += strlen(stats_class_data->instanceIndex[instance]) + 1; // don't forget the NULL!
} // end for - all class instances
if (!(memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
sql_print_error("ExtSQL: Could not alloc %d bytes to write instance data for class %d",
size, classCount);
return(1);
}
// write the data to memPtr
size = 0;
for (instance = 0; instance < maxInstance && stats_class_data->instanceIndex[instance];
instance++) {
strcpy(memPtr + size, stats_class_data->instanceIndex[instance]);
if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: INSTNC (%d/%s)",
instance, memPtr+size);
size += strlen(stats_class_data->instanceIndex[instance]);
size++; // don't forget the NULL!
} // end for - all class instances
if (extsql_write_section_hdr(loadFileName, reloadFile, "INSTNC", size)) {
my_free(memPtr, MYF(0));
return(1);
}
if (my_write(reloadFile, (byte*) memPtr, size,
MYF(MY_WME)) != size) {
sql_print_error("ExtSQL: Could write instance data to RELOAD file %s for %d",
loadFileName, classCount);
my_free(memPtr, MYF(0));
return(1);
}
my_free(memPtr, MYF(0));
} else { // READ
memPtr = NULL;
if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
"INSTNC", classCount))) {
return(1);
}
// allocate memory to hold the instanceIndex array!
if (!(stats_class_data->instanceIndex = (char **)extsql_malloc( ((maxInstance+1) * sizeof(char *)),
MYF(MY_WME | MY_ZEROFILL)) )) {
sql_print_error("ExtSQL: Could not alloc space for instanceIndex, RELOAD file %s for %d",
loadFileName, classCount);
return(1);
}
// move the data from buffer to memory, it is a long sequence of null
// terminated instance names.
// careful, we limit ourselves based not on the max supported, but how many
// instances we really had.
size = 0;
for (instance = 0, srcPtr = memPtr; instance < maxInstance && srcPtr - memPtr < size2; instance++) {
size = strlen(srcPtr) + 1; // don't forget the NULL
if (!(destPtr = extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
sql_print_error("ExtSQL: Memory alloc failed on instance(%d), class(%d)",
instance, classCount);
my_free(memPtr, MYF(0));
return(1);
}
strcpy(destPtr, srcPtr);
stats_class_data->instanceIndex[instance] = destPtr;
srcPtr += size;
if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: INSTNC (%d/%s)",
instance, stats_class_data->instanceIndex[instance]);
// sql_print_information("ExtSQL: On read, instance(%d) at addr (0x%x), at (%s)", instance, destPtr, destPtr);
} // end for - all instance names
my_free(memPtr, MYF(0));
}
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: during %s, completed section INSTNC, class (%d)",
actionStr, classCount);
}
// - "\nEXT VAR___: 2343\n"
// - structure STAT_VAR from STAT_VAR **varIndex
if (action == STATS_WRITE_RELOAD) {
// count the size of what we have, this is an array of pointers to structures, store
// the addr and the string
statPtr = stats_class_data->varIndex;
size = 0;
for (var = 0; var < maxVar; var++, statPtr++) {
size += sizeof(char *) + strlen((*statPtr)->name) + 1;
}
if (!(memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
sql_print_error("ExtSQL: Could not alloc %d bytes to write varIndex data for class %d",
size, classCount);
return(1);
}
// write the data to buffPtr (address, followed by null term name)
statPtr = stats_class_data->varIndex;
size = 0;
for (var = 0; var < maxVar; var++, statPtr++) {
ptrPtr = (char **)(memPtr+size);
*ptrPtr = (*statPtr)->addr; // ZZ - var addr may not match on restart?
size += sizeof (char *);
strcpy(memPtr+size, (*statPtr)->name);
size += strlen((*statPtr)->name);
if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: VAR (%d / %s / 0x%x)",
var, (*statPtr)->name, (uint)(*statPtr)->addr);
*(memPtr+size++) = '\0'; // don't forget the NULL
}
if (extsql_write_section_hdr(loadFileName, reloadFile, "VAR___", size)) {
my_free(memPtr, MYF(0));
return(1);
}
if (my_write(reloadFile, (byte*) memPtr, size,
MYF(MY_WME)) != size) {
sql_print_error("ExtSQL: Could write varIndexdata to RELOAD file %s for %d",
loadFileName, classCount);
my_free(memPtr, MYF(0));
return(1);
}
my_free(memPtr, MYF(0));
} else { // READ
memPtr = NULL;
if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
"VAR___", classCount))) {
return(1);
}
// alloc memory for the complete var index of pointers
if (!(stats_class_data->varIndex = (STAT_VAR **)extsql_malloc( ((maxVar+1) * sizeof(pSTAT_VAR)),
MYF(MY_WME | MY_ZEROFILL)) )) {
sql_print_error("ExtSQL: Could not alloc space for varIndex, RELOAD file %s for %d",
loadFileName, classCount);
return(1);
}
// move the data from buff to memory, careful that we don't exceed what
// we actually read in.
statPtr = stats_class_data->varIndex;
size = 0;
for (var = 0, srcPtr = memPtr; srcPtr - memPtr < size2; var++, statPtr++) {
// allocate room to the STAT_VAR structure we will point to and that will hold
// the name and addr
if (!(*statPtr = (STAT_VAR *)extsql_malloc( (sizeof(STAT_VAR)),
MYF(MY_WME | MY_ZEROFILL)) )) {
sql_print_error("ExtSQL: Could not alloc space for STAT_VAR structure, RELOAD file %s for %d",
loadFileName, classCount);
return(1);
}
ptrPtr = (char **)srcPtr;
(*statPtr)->addr = *ptrPtr;
// move to name
size = sizeof (char *);
srcPtr += size; // start of name
size = strlen(srcPtr) + 1; // length
if (!(destPtr = extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
sql_print_error("ExtSQL: Memory alloc failed on var(%d), class(%d)",
var, classCount);
my_free(memPtr, MYF(0));
return(1);
}
strcpy(destPtr, srcPtr); // copy name to allocated memory
(*statPtr)->name = destPtr; // set pointer
// need to store the address in our quick lookup array, it would have been cleared
// prior to the reload
if (extsql_store_var_addr((*statPtr)->addr )) {
return(1);
}
if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: VAR (%d / %s / 0x%x)",
var, (*statPtr)->name, (uint)(*statPtr)->addr);
srcPtr += size; // move to next addr/name pair
} // end for - all vars
my_free(memPtr, MYF(0));
} // end if - else
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: during %s, completed section VAR___, class (%d)",
actionStr, classCount);
}
// - "\nEXT TIME: 2123\n"
// - my_time_t array of time entries for each data entry from my_time_t *timeLabels
if (action == STATS_WRITE_RELOAD) {
// count the size of what we have, this is an array of time labels,
// just items of my_time_t all in a row
size = sizeof(my_time_t) * maxTime;
if (extsql_write_section_hdr(loadFileName, reloadFile, "TIME__", size)) {
return(1);
}
if STATS_DEBUG(STATS_RELOAD) {
strcpy(buff, "ExtSQL: TIME ");
// only the first 10!
for (i = 0; i < maxTime && i < 10; i++) {
sprintf(buff+strlen(buff), "(%d / %d)", i, (int)stats_class_data->timeLabels[i]);
}
sql_print_information(buff);
}
if (my_write(reloadFile, (byte*) stats_class_data->timeLabels, size,
MYF(MY_WME)) != size) {
sql_print_error("ExtSQL: Could write timeLabels data to RELOAD file %s for %d",
loadFileName, classCount);
return(1);
}
// just to satisfy compiler that extsql_print_bytes could be used!
if (!strcmp(loadFileName, "bongongongog")) {
extsql_print_bytes((char *)stats_class_data->timeLabels, size);
}
} else { // READ
// this one is easy, just read it right in, space has already been allocated!
memPtr = NULL;
if (!extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
"TIME__", classCount)) {
return(1);
}
// DON'T FREE memPtr here
stats_class_data->timeLabels = (my_time_t *) memPtr;
if STATS_DEBUG(STATS_RELOAD) {
strcpy(buff, "ExtSQL: TIME ");
// only the first 10!
for (i = 0; i < maxTime && i < 10; i++) {
sprintf(buff+strlen(buff), "(%d / %d)", i, (int)stats_class_data->timeLabels[i]);
}
sql_print_information(buff);
}
} // end if - else
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: during %s, completed section TIME__, class (%d)",
actionStr, classCount);
}
// - "\nEXT DATA__: 000342432\n"
// - ulong data (actual recorded data) from ulong *data
// get the size of the data section
size = stats_class_data->maxInstance * stats_class_data->maxVar *
stats_class_data->maxTime * sizeof(ulong);
if (action == STATS_WRITE_RELOAD) {
// sanity check, size should be same as diff, remember offset in dataEnd
// is as an index to ulong entries, size is in bytes!
size2 = (stats_class_data->dataEnd - stats_class_data->data) * sizeof(ulong);
if (size != size2) {
sql_print_error("ExtSQL: data size error (%d/%d) for class %d",
size, size2, classCount);
return(1);
}
if (extsql_write_section_hdr(loadFileName, reloadFile, "DATA__", size)) {
return(1);
}
if (my_write(reloadFile, (byte*) stats_class_data->data, size,
MYF(MY_WME)) != size) {
sql_print_error("ExtSQL: Could write ext data to RELOAD file %s for %d",
loadFileName, classCount);
return(1);
}
} else { // READ
// this one is easy, just read it right in!
memPtr = NULL;
if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr,
"DATA__", classCount)) ) {
return(1);
}
// sanity check, same size?
if (size != size2) {
sql_print_error("ExtSQL: data size error (%d/%d) for class %d",
size, size2, classCount);
my_free(memPtr, MYF(0));
return(1);
}
// DON'T FREE memPtr here
stats_class_data->data = (ulong *) memPtr;
// set dataEnd
stats_class_data->dataEnd = stats_class_data->data + size/sizeof(ulong);
} // end if -else
if STATS_DEBUG(STATS_RELOAD) {
sql_print_information("ExtSQL: during %s, completed section DATA__, class (%d)",
actionStr, classCount);
}
// - "\nEXT CLASS n: 2\n" (repeat part for each class)
} // end for - all classes
return(0);
} // end extsql_write_reload
// extsql_store_var_addr - stores just the address of a tracked var in a single array
// this makes increment processing much quicker on the lookup to see if we are
// tracking a variable
static int extsql_store_var_addr(char *varAddr) {
int k, varAddrFound;
for (k = 0, varAddrFound = 0; k < Shared->varAddrCount; k++) {
if (Shared->varAddrTrackArray[k] == varAddr) { // it's there
varAddrFound = 1;
break;
}
} // end for - existing array values
if (!varAddrFound && k < MAX_VARS) { // not there and not at end, add it
Shared->varAddrTrackArray[k] = varAddr;
Shared->varAddrCount++;
} else if (!varAddrFound && k >= MAX_VARS) {
sql_print_error("ExtSQL: disabled, var tracking limit of %d exceeded",
MAX_VARS);
return(1);
}
return(0);
} // end extsql_store_var_addr
// print out all the internal data structures
static void extsql_stats_dump() {
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **statPtr;
int i, time, maxVar, maxTime, maxInstance, var, value, instance, classCount;
char buff[STATS_TMP_BUFF_SIZE];
// DEBUG LOG TO FILE
// general info
sql_print_information("ExtSQL: DUMP START (%d)", (int)StartTime);
sql_print_information(" version (%s)", Shared->extsql_version+17); // don't reprint version
sql_print_information(" extsql_class_string (%s)", Shared->extsql_class_list);
sql_print_information(" extsql_users (%s)", extsql_users);
// varAddrTrackArray
sprintf(buff," varAddrTrackArray (%d): ", Shared->varAddrCount);
for (i = 0; i < MAX_VARS && i < 40 && i < Shared->varAddrCount; i++) {
sprintf(buff + strlen(buff),"(0x%x) ", (uint) Shared->varAddrTrackArray[i]);
}
sql_print_information(buff);
// loop and print everything to the log file
classCount = 0;
for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
maxVar = stats_class_data->maxVar;
maxTime = stats_class_data->maxTime;
maxInstance = stats_class_data->maxInstance;
// print out summary info on the CLASS
sql_print_information("CLASS is %s, maxInstance(%d), maxVar(%d), maxTime(%d), time(%d), \
timeUnits(%c), lastTime(%d)",
stats_class_data->name, maxInstance, maxVar, maxTime,
stats_class_data->time, stats_class_data->timeUnits,
stats_class_data->lastTime);
sql_print_information(" Base of instanceIndex array at 0x%x", (uint)stats_class_data->instanceIndex);
for (instance=0;
instance < maxInstance && stats_class_data->instanceIndex[instance];
instance++) {
sql_print_information(" instanceIndex[%d] = (%s), addr(0x%x)",
instance, stats_class_data->instanceIndex[instance],
(uint) stats_class_data->instanceIndex[instance]);
} // end for -- all instances
for (var=0, statPtr = stats_class_data->varIndex;
(var < stats_class_data->maxVar) && *statPtr ;
var++, statPtr++) {
sql_print_information(" varIndex[0x%x] = name(%s), addr(0x%x)",
var, (*statPtr)->name, (uint)(*statPtr)->addr);
} // end for - all vars
for (instance=0; instance < maxInstance; instance++) {
for (var=0, statPtr = stats_class_data->varIndex;
var < stats_class_data->maxVar && *statPtr;
var++, statPtr++) {
for (time = 0; time < maxTime; time++) {
value = stats_class_data->data [ARRAY_INDEX(instance, var, maxVar,
time, maxTime)];
if (value) {
sql_print_information(" statData[%d-%s][%d-%s][%d](%d) = %d",
instance, stats_class_data->instanceIndex[instance],
var, (*statPtr)->name,
time, (int)stats_class_data->timeLabels[time],
value);
} // end if - value
} // end for - all times
} // end for - all variables
} // end for - all instances
} // end for - log file write all classes
sql_print_information("ExtSQL: DUMP END ");
} // end extsql_stats_dump
// dump/check thread info
// if doDump is set, then go ahead and dump all info on all threads;
// otherwise just create output if there is an error.
static void extsql_thread_dump(THD *thd, int doDump)
{
#ifndef PGSQL
if (pthread_mutex_trylock(&LOCK_thread_count)) { // forget about it!
return;
}; // For unlink from list
#endif
#ifdef PGSQL
// NEED this ZZ
#else
I_List_iterator it(threads);
THD *tmp;
pthread_t my_id = pthread_self();
int foundError = 0;
char *user, *host;
int *statVerify, *indexPtr;
if (doDump) {
sql_print_information("ExtSQL: this thread id is %d", thd->thread_id);
}
while ((tmp=it++))
{
#ifdef EXTSQL_50
user = tmp->security_ctx->user;
host = tmp->security_ctx->host;
statVerify = &tmp->security_ctx->statVerify;
indexPtr = tmp->security_ctx->statIndex;
#else
user = tmp->user;
host = tmp->host;
statVerify = &tmp->statVerify;
indexPtr = tmp->statIndex;
#endif
if (doDump) {
sql_print_information("ExtSQL: thd(%d/%s/%s) user(%s) host(%s) db(%s) magic(0x%x)",
tmp->thread_id, tmp->proc_info, command_name[tmp->command], user,
host, tmp->db, *statVerify);
}
for (int i = 0; i < STATS_MAX_CLASSES; i++) {
if (indexPtr[i]) {
if ( (*statVerify == STATS_MAGIC_NUM) &&
( (indexPtr[i] >= Shared->statData[i].maxInstance) || indexPtr[i] < 0) ) {
sql_print_warning("ExtSQL: special check failed: thd(%d/%s/%s) user(%s) host(%s) db(%s) magic(0x%x)",
tmp->thread_id, tmp->proc_info, command_name[tmp->command], user,
host, tmp->db, *statVerify);
sql_print_warning(" index(%d), value(0x%x/0x%x)",
i, indexPtr[i], Shared->statData[i].maxInstance);
}
if (doDump) {
sql_print_information(" index(%d), value(0x%x/0x%x)",
i, indexPtr[i], Shared->statData[i].maxInstance);
} // end if - dump value
} // end if - has value
} // end for - all entries in this thread
} // end while - still more threads
#endif
#ifndef PGSQL
VOID(pthread_mutex_unlock(&LOCK_thread_count));
#endif
} // end extsql_thread_dump
// a new thread has been created, init the stat tracking structures
// if (initType == STATS_THD_INIT_DB) is set, the thread exists, but a 'use' command was given to change DB's
// we need to update the db index ONLY!
// if initType == STATS_THD_INIT_ALL -- we reset everybody no matter what!
int extsql_thread_init(THD *thd, int initType)
{
int i, threadID, command;
char *tmpName = "", classStart;
int classCount = 0;
int found = 0;
pSTATS_CLASS_DATA stats_class_data;
char *user = "", *host = "", *db = "", *proc_info = "", **instPtr;
int *statVerify = 0, *indexPtr = 0;
char tmpBuff[STATS_TMP_BUFF_SIZE];
if (initType != STATS_THD_INIT_ALL) { // normal
if (!Shared->extsql_active || Shared->extsql_disabled) { // shouldn't normally even get here
return(0);
}
if (initType == STATS_THD_INIT_DB) { // ZZ -return
// return(0);
}
}
// attach to our shared memory!
if (extsql_init_globals()) {
return(1);
}
if STATS_DEBUG(STATS_DUMP_START) {
extsql_stats_dump();
}
StartTime = time(NULL);
extsql_init_proc_vars(&threadID, &command, &user, &db, &host, &proc_info, &indexPtr, &statVerify);
// OKAY, if (initType == STATS_THD_INIT_DB) is set we should already be tracking data on this thread, just
// need to change the db index - SO, we should have already done an init on this
// thread and the MAGIC should have been set, if not, get out!
if (initType == STATS_THD_INIT_DB) {
if (*statVerify != STATS_MAGIC_NUM) {
return(1);
}
}
// check that we have a host and db
if (! host || ! *host || *host == '\0') {
host = "(null)";
}
if (! db || ! *db || *db == '\0') {
db = "(null)";
}
// we don't do thread zero -- ZZ disabled temp
if (0 && ! threadID) {
sql_print_warning("ExtSQL: thread_init called for thread zero in error");
return(0);
}
// for root stuff, i.e. phpMyAdmin, the db is (null).
if STATS_DEBUG(STATS_THD_INIT_PARAMS) {
sql_print_information("ExtSQL: thread (%d) started for db %s(0x%x), user %s(0x%x), host %s(0x%x)",
threadID, db, (uint)db, user, (uint)user, host, (uint)host);
}
// for quick indexing on increment, we store the index values used to track the instances
// used by this thread within the thread itself
// see if user and db names are there already
// check/add indexes depending on classes that we are collecting for;
// init the class index array to zero if this is a first time init or for ALL
if (!(initType == STATS_THD_INIT_DB)) {
for (i = 0; i< STATS_MAX_CLASSES; i++) {
indexPtr[i] = 0;
}
*statVerify = 0; // will be set below when we find match on anything.
}
for (stats_class_data = Shared->statData, classCount = 0;
classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance;
stats_class_data++, classCount++) {
classStart = stats_class_data->name[0];
if ((initType == STATS_THD_INIT_DB) && classStart != 'd' && classStart != 'c') { // we just want the db or condb
continue;
}
switch (classStart) { // LIST should match extsqlClassesSupported[]
case 'c': // connect
if ((initType == STATS_THD_INIT_DB) && stats_class_data->name[3] != 'd') { // not condb
continue;
}
if (stats_class_data->name[3] == 'd') { // condb - db@host
strcpy(tmpBuff, db);
} else if (stats_class_data->name[3] == 'u') { // conuser- user@host
strcpy(tmpBuff, user);
} else {
sql_print_error("ExtSQL: disabled, unexpected connection class name %s",
stats_class_data->name);
Shared->extsql_disabled = 1;
break;
}
strcat(tmpBuff, "@");
strcat(tmpBuff, host);
tmpName = tmpBuff;
break;
case 'd': // db
tmpName = db;
break;
case 'h': // host
tmpName = host;
break;
case 's': // server -- cum total of all on this server
tmpName = "server";
break;
case 'u': // user
tmpName = user;
break;
default:
sql_print_error("ExtSQL: disabled, unexpected class name %s",
stats_class_data->name);
Shared->extsql_disabled = 1;
} // end switch
// make sure we got something in tmpName
if (!tmpName) {
// sql_print_warning("ExtSQL: got null tmpName instance value for class %s, forcing to (null)", stats_class_data->name);
tmpName = "(null)";
}
if (Shared->extsql_disabled) {
break;
}
// now try to match or insert
// check for new or existing instance, LOCK statData during the search
found = 0;
i = 0;
for (instPtr = (char **)stats_class_data->instanceIndex;
i < stats_class_data->maxInstance; instPtr++, i++) {
if STATS_DEBUG(STATS_THD_INIT_LOOP) {
sql_print_information(" Looking for match of %s with %s", *instPtr, tmpName);
}
if (!*instPtr) { // at a blank, fill it
// Grab the lock now, check to see if it is still NULL
// if it is, fill it in
(void) pthread_mutex_lock(&Shared->LOCK_stats_load); // IMPORTANT, don't leave with unlock!
if (!*instPtr) { // at a blank, fill it
if (!(*instPtr = extsql_strdup(tmpName, MYF(MY_WME)))) {
Shared->extsql_disabled = 1;
sql_print_error("ExtSQL: disabled, unable to alloc instance item %s",
tmpName);
// UNLOCK statData
(void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!
break;
}
found = 1;
Shared->statsAllocArray[Shared->allocIndex] = *instPtr;
// UNLOCK statData
(void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!
indexPtr[classCount] = i;
if STATS_DEBUG(STATS_THD_INIT_LOOP) {
sql_print_information("ExtSQL, NEW statIndex[%d]->%d for %s in class %s stored as (%s)",
classCount, i, tmpName, stats_class_data->name,
Shared->statsAllocArray[Shared->allocIndex]);
}
Shared->allocIndex++;
break;
} // end if - was emtpy - fill in new values
// if we get here it is not null, someone else got it ahead of us. May have filled in another value,
// or what we want. -- we release lock, we will fall throught to below and check for a match, and keep looping
// UNLOCK statData
(void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!
} // end if - was empty - fill in new value
if (!strcmp(*instPtr, tmpName)) { // match
indexPtr[classCount] = i;
found = 1;
break;
}
} // end for - each instance
// check for max instances exceeded
if (! found) {
sql_print_warning("ExtSQL: disabled, instances exceed tracking limit for class %s with %s",
stats_class_data->name, tmpName);
Shared->extsql_disabled = 1;
break;
}
// if we get here, something was found.
*statVerify = STATS_MAGIC_NUM;
if (Shared->extsql_disabled) {
break;
}
} // end for - each class
if STATS_DEBUG(STATS_DUMP_START) {
extsql_stats_dump();
}
#ifdef PGSQL
extsql_inc(&Connections, 1);
#endif
return(0);
} // end extsql_thread_init
#ifdef EXTSQL_50
// only for information schema, in 5 and above
extern bool schema_table_store_record(THD *thd, TABLE *table);
// We fill the table with our tracking data, depending on the name
int fill_schema_extsql(THD *thd, TABLE_LIST *tables, COND *cond)
{
const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
TABLE *table= tables->table;
CHARSET_INFO *scs= system_charset_info;
pSTATS_CLASS_DATA stats_class_data;
STAT_VAR **statPtr;
ST_SCHEMA_TABLE *schema_table= tables->schema_table;
int found, var, maxVar, maxTime, maxInstance, currentTime, timeLimit, timeCount, indexTime,
varNameLength, varIndex, value, stats_hourly, fieldIndex=0, rowHasData, doZeroTime=1;
char listBuff[MAX_BUFF_SIZE], className[STATS_MAX_NAME];
char *varListPtr = listBuff, *stats_class = className, *varName, *termChar;
MYSQL_TIME now_mysql_time;
char *user;
DBUG_ENTER("schema_table_store_record");
// are they authorized
#ifdef EXTSQL_50
user = thd->security_ctx->user;
#else
user = thd->user;
#endif
// return if not authorized, disabled or inactive
if (extsql_check(thd, 0)) DBUG_RETURN(1);
// lets make sure this is one of our tables
if (!strncmp(schema_table->table_name, "EXTSTATS_", 9)) { // one of ours
int found, var, maxVar;
// lets get the class name off the end
strcpy(stats_class, schema_table->table_name + 9);
// find the data for the Class we are interested in
found = 0;
for (stats_class_data = Shared->statData; stats_class_data->maxInstance;
stats_class_data++) {
if (!strcmp(stats_class_data->name, stats_class)) { // found it
found = 1;
break;
}
} // end for
if (!found) {
net_printf_error(thd, 0, "ExtSQL: can't fill requested SCHEMA TABLE %s(%s). The \
most likely reason is that no data is being recorded for that class. Use SHOW STATISTICS to see \
what is being tracked.",
schema_table->table_name, stats_class);
DBUG_RETURN(0);
}
} else { // called by mistake
net_printf_error(thd, 0, "ExtSQL: schema called in error to fill table %s.",
schema_table->table_name);
DBUG_RETURN(0);
}
// AT THIS POINT stats_class_data points to the requested class
maxVar = stats_class_data->maxVar;
maxTime = stats_class_data->maxTime;
maxInstance = stats_class_data->maxInstance; // needed for array indexing
currentTime = stats_class_data->time; // time we are currently logging to.
timeLimit = maxTime-1; // they get it all
// we need to now load the columns in the PROPER order, data is recorded as it
// occurs, but the Var name order is controlled by the user, we try to use
// that order.
strcpy(varListPtr,","); // init the list
// loop through the instance variables for names
for (var=0, statPtr = stats_class_data->varIndex;
(var < stats_class_data->maxVar) && *statPtr ;
var++, statPtr++) {
strcat(varListPtr,(*statPtr)->name );
strcat(varListPtr, ",");
if (strlen(varListPtr) > MAX_BUFF_SIZE - 50) {
net_printf(thd, 0, "ExtSQL: List of Vars too long");
DBUG_RETURN(1);
}
} // end for - all instances
// Remember, zero index holds the total. The
// currentTime has our present location and it is circular, i.e. if
// maxTime for the class is 4, we store current + 3. If the currentTime
// is 2, the proper way to display all hours, starting with the most
// recent is to show 2(current),1(prior),3(more prior) - skip 0
// we now go through all the instances, we will be loading the table
// with instance_name, time, var, var, .....
for (int instance=0; instance < maxInstance && stats_class_data->instanceIndex[instance] ;
instance++) {
// loop through the times,
// if stats_timely is set we will output a number of hours starting at currentTime, and
// limited by timeLimit (starts at 1).
// For INFORMATION_SCHEMA we have a real problem, need to print the totals under
// time heading of 0000-00-00 00:00 (zero time), but then pick up and print the rest,
// have to really hack the loop control to make first output special, zero time.
// If doZeroTime = 1 -- we need to output the zero values(totals)
doZeroTime = 1;
for (timeCount = 0, indexTime = currentTime ;
timeCount < timeLimit && indexTime >= 0;
timeCount++, indexTime-- ) {
// we have to adjust our indexTime for wrap
if (!indexTime && !doZeroTime) { // we have gone back to the total
indexTime = maxTime - 1; // pick the last
}
restore_record(table, s->default_values);
fieldIndex = 0;
rowHasData = 0; // do we have anything to show, only on HISTORY
// first column - instance name - always
table->field[fieldIndex++]->store(stats_class_data->instanceIndex[instance],
strlen(stats_class_data->instanceIndex[instance]), scs);
// second column is times - always, we use indexTime
if (indexTime) {
thd->variables.time_zone->gmt_sec_to_TIME(&now_mysql_time,
(my_time_t) stats_class_data->timeLabels[indexTime]);
} else {
char *zeroTime = "0000-00-00 00:00:00";
str_to_time_with_warn(zeroTime, strlen(zeroTime), &now_mysql_time);
}
table->field[fieldIndex++]->store_time(&now_mysql_time, MYSQL_TIMESTAMP_DATETIME);
// loop through varListPtr, output the vars in the DEFAULT order
// varListPtr = ",bytes,selects," varName will move through this
for ( varName = varListPtr; *varName ; varName = strstr(varName, ",")) {
varName++; // move off the comma
//sql_print_information(" target varName is (%s)", varName);
if (! *varName) { // we are done
break;
}
varNameLength = strchr(varName, ',') - varName;
// okay, varName points at a name, lets go through the index looking
// for a match
found = 0;
for (varIndex=0, statPtr = stats_class_data->varIndex;
varIndex < maxVar && *statPtr;
varIndex++, statPtr++) {
termChar = (*statPtr)->name + varNameLength +1; // point to end of the current var name
if (!strncmp( (*statPtr)->name, varName, varNameLength)) {
found = 1;
value = stats_class_data->data [ARRAY_INDEX(instance, varIndex, maxVar,
indexTime, maxTime)];
table->field[fieldIndex++]->store((longlong) value, TRUE);
if (value) { // is it non-zero
rowHasData = 1;
}
break;
}
} // end for - a matching var in the list
if (!found) {
table->field[fieldIndex++]->store((longlong) 9999999, TRUE);
}
} // end for - requested values
if (!indexTime) { // it had hit zero and we have outputed totals
doZeroTime = 0; // don't do it again for this class
indexTime = 1; // we want it to zero again for wrap.
timeCount--; // this row doesn't count
}
if (!rowHasData) {
continue;
}
// record the row
if (schema_table_store_record(thd, table)) {
sql_print_error("ExtSQL, protocol write failed");
DBUG_RETURN(1);
}
} // end for - all requested times
} // end for - all instances
return 0;
}
#endif
|