You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/lib/debug.cpp

587 lines
19 KiB

#include "proxysql.h"
#include "proxysql_atomic.h"
#include "sqlite3db.h"
#include "prometheus_helpers.h"
#include "gen_utils.h"
#include <set>
#include <cxxabi.h>
#include <string>
#include <unordered_map>
#include <array>
using std::string;
using std::unordered_map;
#ifdef DEBUG
__thread unsigned long long pretime=0;
static pthread_mutex_t debug_mutex;
static pthread_rwlock_t filters_rwlock = PTHREAD_RWLOCK_INITIALIZER;
static SQLite3DB * debugdb_disk = NULL;
sqlite3_stmt *statement1=NULL;
static stmt_unique_ptr statement1_unique {};
static unsigned int debug_output = 1;
#define DEBUG_MSG_MAXSIZE 1024
struct DebugLogEntry {
unsigned long long time = 0;
unsigned long long lapse = 0;
int thr = 0;
std::string file;
int line = 0;
std::string funct;
int module = 0;
std::string modname;
int verbosity = 0;
std::string message;
std::string backtrace;
};
static const size_t limitSize = 100;
/*
* NOTE: Intentionally leaked.
* During fast shutdown paths we can call exit() while worker threads are still
* emitting debug logs. A static std::vector would be destroyed by exit handlers
* and could race with concurrent push_back(), causing use-after-free.
*/
static std::vector<DebugLogEntry>* log_buffer = new std::vector<DebugLogEntry>();
/**
* @brief Synchronizes the log buffer to the SQLite database.
*
* This function writes the contents of the shared log buffer to the SQLite database
* in a single transaction. It assumes that the log buffer is protected by a mutex
* and that the prepared statement `statement1` has already been prepared.
* This happens in proxy_debug_func()
*
* @param db A pointer to the SQLite3DB object.
*/
void sync_log_buffer_to_disk(SQLite3DB *db) {
int rc;
db->execute("BEGIN TRANSACTION");
for (const auto& entry : *log_buffer) {
rc=(*proxy_sqlite3_bind_int64)(statement1, 1, entry.time); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_int64)(statement1, 2, entry.lapse);ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_int64)(statement1, 3, entry.thr); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 4, entry.file.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_int64)(statement1, 5, entry.line); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 6, entry.funct.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_int64)(statement1, 7, entry.module); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 8, entry.modname.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_int64)(statement1, 9, entry.verbosity); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 10, entry.message.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
rc=(*proxy_sqlite3_bind_text)(statement1, 11, entry.backtrace.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db);
SAFE_SQLITE3_STEP2(statement1);
rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db);
// Note: no assert() in proxy_debug_func() after (*proxy_sqlite3_reset)() because it is possible that we are in shutdown
rc=(*proxy_sqlite3_reset)(statement1); // ASSERT_SQLITE_OK(rc, db);
}
db->execute("COMMIT");
log_buffer->clear();
}
/**
* @brief Contains all filters related to debug.
* @details The convention for key value is `filename:line:function`. This key structure also applies also
* applies if the line is `0` or function is empty, the `filename` is always mandatory.
*
* IMPORTANT: This structure is a pointer to avoid race conditions during process termination, otherwise the
* destruction of the object may be performed before working threads have exited. This structure will leak,
* this is intentional, since we can't synchronize the exit of the working threads with its destruction.
*/
std::set<std::string>* debug_filters = nullptr;
static bool filter_debug_entry(const char *__file, int __line, const char *__func) {
pthread_rwlock_rdlock(&filters_rwlock);
bool to_filter = false;
if (debug_filters && debug_filters->size()) { // if the set is empty we aren't performing any filter, so we won't search
std::string key(__file);
key += ":" + std::to_string(__line);
key += ":";
key += __func;
// we start with a full search
if (debug_filters->find(key) != debug_filters->end()) {
to_filter = true;
} else {
// we now search filename + line
key = __file;
key += ":" + std::to_string(__line);
// remember to add the final ":"
key += ":";
if (debug_filters->find(key) != debug_filters->end()) {
to_filter = true;
} else {
// we now search filename + function
key = __file;
// no line = 0
key += ":0:";
key += __func;
if (debug_filters->find(key) != debug_filters->end()) {
to_filter = true;
} else {
// we now search filename only
key = __file;
// remember to add ":" even if no line
key += ":0:";
if (debug_filters->find(key) != debug_filters->end()) {
to_filter = true;
} else {
// if we reached here, we couldn't find any filter
}
}
}
}
}
pthread_rwlock_unlock(&filters_rwlock);
return to_filter;
}
// we use this function to sent the filters to Admin
// we hold here the lock on filters_rwlock
void proxy_debug_get_filters(std::set<std::string>& f) {
pthread_rwlock_rdlock(&filters_rwlock);
if (debug_filters) {
f = *debug_filters;
}
pthread_rwlock_unlock(&filters_rwlock);
}
// we use this function to get the filters from Admin
// we hold here the lock on filters_rwlock
void proxy_debug_load_filters(std::set<std::string>& f) {
pthread_rwlock_wrlock(&filters_rwlock);
if (debug_filters) {
debug_filters->erase(debug_filters->begin(), debug_filters->end());
*debug_filters = f;
} else {
debug_filters = new std::set<std::string>(f);
}
pthread_rwlock_unlock(&filters_rwlock);
}
// REMINDER: This function should always save/restore 'errno', otherwise it could influence error handling.
void proxy_debug_func(
enum debug_module module,
int verbosity,
int thr,
const char *__file,
int __line,
const char *__func,
const char *fmt,
...
) {
assert(module<PROXY_DEBUG_UNKNOWN);
// Safety check: ensure debug struct is initialized
if (GloVars.global.gdbg_lvl == NULL) {
return;
}
if (pretime == 0) { // never initialized
pretime=realtime_time();
}
if (GloVars.global.gdbg_lvl[module].verbosity < verbosity) {
return;
}
// Save errno only after passing early return checks
int saved_errno = errno;
// check if the entry must be filtered
if (filter_debug_entry(__file, __line, __func)) {
errno = saved_errno;
return;
}
char origdebugbuff[DEBUG_MSG_MAXSIZE];
char debugbuff[DEBUG_MSG_MAXSIZE];
char longdebugbuff[DEBUG_MSG_MAXSIZE*8];
char longdebugbuff2[DEBUG_MSG_MAXSIZE*8];
longdebugbuff[0]=0;
longdebugbuff2[0]=0;
unsigned long long curtime=realtime_time();
bool write_to_disk = false;
if (debugdb_disk != NULL && (debug_output == 2 || debug_output == 3)) {
write_to_disk = true;
}
if (
GloVars.global.foreground
||
write_to_disk == true
) {
va_list ap;
va_start(ap, fmt);
vsnprintf(origdebugbuff, DEBUG_MSG_MAXSIZE,fmt,ap);
va_end(ap);
sprintf(longdebugbuff, "%llu(%llu): %d:%s:%d:%s(): MOD#%d#%s LVL#%d : %s" , curtime, curtime-pretime, thr, __file, __line, __func, module, GloVars.global.gdbg_lvl[module].name, verbosity, origdebugbuff);
}
#ifdef __GLIBC__
if (GloVars.global.gdbg_lvl[module].verbosity>=10) {
void *arr[20];
char **strings;
int s;
s = backtrace(arr, 20);
strings=backtrace_symbols(arr,s);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (int i=0; i<s; i++) {
debugbuff[0]=0;
sscanf(strings[i], "%*[^(](%100[^+]", debugbuff);
int status;
char *realname=NULL;
realname=abi::__cxa_demangle(debugbuff, 0, 0, &status);
if (realname) {
sprintf(debugbuff," ---- %s : %s\n", strings[i], realname);
strcat(longdebugbuff2,debugbuff);
}
}
free(strings);
}
#endif
pthread_mutex_lock(&debug_mutex);
if (debugdb_disk == NULL) {
// default behavior
if (longdebugbuff[0] != 0) {
fprintf(stderr, "%s", longdebugbuff);
}
if (longdebugbuff2[0] != 0) {
if (GloVars.global.gdbg_lvl[module].verbosity>=10) {
fprintf(stderr, "%s\n", longdebugbuff2);
}
}
} else {
SQLite3DB *db = debugdb_disk;
int rc = 0;
if (statement1==NULL) {
const char *a = "INSERT INTO debug_log (id, time, lapse, thread, file, line, funct, modnum, modname, verbosity, message, note, backtrace) VALUES (NULL, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL, ?11)";
auto [stmt_rc, prepared_statement_unique] = db->prepare_v2(a);
rc = stmt_rc;
ASSERT_SQLITE_OK(rc, db);
statement1_unique = std::move(prepared_statement_unique);
statement1 = statement1_unique.get();
}
if (debug_output == 1 || debug_output == 3) {
// to stderr
if (longdebugbuff[0] != 0) {
fprintf(stderr, "%s", longdebugbuff);
}
if (longdebugbuff2[0] != 0) {
fprintf(stderr, "%s", longdebugbuff2);
}
}
if (write_to_disk == true) {
// Create a DebugLogEntry
DebugLogEntry entry;
entry.time = curtime;
entry.lapse = curtime - pretime;
entry.thr = thr;
entry.file = __file;
entry.line = __line;
entry.funct = __func;
entry.module = module;
entry.modname = GloVars.global.gdbg_lvl[module].name;
entry.verbosity = verbosity;
entry.message = origdebugbuff;
entry.backtrace = longdebugbuff2;
log_buffer->push_back(entry);
// we now batch writes
// note1: in case of crash, the database will have some missing entries,
// but the entries can be read in `log_buffer` in the core dump
// note2: also in case of shutdown , `log_buffer` will have entries that won't be saved.
// if we really want *all* entries, we could just call sync_log_buffer_to_disk() on shutdown
if (
(log_buffer->size() >= limitSize)
||
(entry.file == "ProxySQL_Admin.cpp" && entry.funct == "flush_logs")
) {
sync_log_buffer_to_disk(db);
}
}
}
pthread_mutex_unlock(&debug_mutex);
if (curtime != 0)
pretime=curtime;
errno = saved_errno;
};
#endif
using metric_name = std::string;
using metric_help = std::string;
using metric_tags = std::map<std::string, std::string>;
using debug_dyn_counter_tuple = std::tuple<p_debug_dyn_counter::metric, metric_name, metric_help, metric_tags>;
using debug_dyn_counter_vector = std::vector<debug_dyn_counter_tuple>;
const std::tuple<debug_dyn_counter_vector> debug_metrics_map = std::make_tuple(
debug_dyn_counter_vector {
std::make_tuple (
p_debug_dyn_counter::proxysql_message_count,
"proxysql_message_count_total",
"Number of times a particular message has been logged by ProxySQL.",
metric_tags {}
)
}
);
std::map<std::string, prometheus::Counter*> p_proxysql_messages_map {};
std::array<prometheus::Family<prometheus::Counter>*, p_debug_dyn_counter::__size> p_debug_dyn_counter_array {};
std::mutex msg_stats_mutex {};
const int ProxySQL_MSG_STATS_FIELD_NUM = 7;
class ProxySQL_messages_stats {
public:
const int32_t message_id = 0;
const char* filename = nullptr;
const int32_t line = 0;
const char* func = nullptr;
uint64_t count_star = 0;
time_t first_seen = 0;
time_t last_seen = 0;
ProxySQL_messages_stats(
const int32_t message_id_, const char* filename_, const int32_t line_, const char* func_, time_t first_seen_,
time_t last_seen_, uint64_t count_star_
) : message_id(message_id_), filename(filename_), line(line_), func(func_), count_star(count_star_),
first_seen(first_seen_), last_seen(last_seen_)
{
assert(message_id_);
assert(filename_);
assert(line_);
assert(func_);
}
char** get_row() const {
char buf[128];
char** pta = static_cast<char**>(malloc(sizeof(char *)*ProxySQL_MSG_STATS_FIELD_NUM));
sprintf(buf,"%d",message_id);
pta[0]=strdup(buf);
pta[1]=strdup(filename);
sprintf(buf,"%d",line);
pta[2]=strdup(buf);
pta[3]=strdup(func);
sprintf(buf,"%lu",count_star);
pta[4]=strdup(buf);
sprintf(buf,"%ld", first_seen);
pta[5]=strdup(buf);
sprintf(buf,"%ld", last_seen);
pta[6]=strdup(buf);
return pta;
}
void add_time(uint64_t n) {
count_star++;
if (first_seen == 0) {
first_seen = n;
}
last_seen=n;
}
void free_row(char **pta) const {
for (int i=0; i < ProxySQL_MSG_STATS_FIELD_NUM; i++) {
assert(pta[i]);
free(pta[i]);
}
free(pta);
}
};
unordered_map<string, ProxySQL_messages_stats> umap_msg_stats {};
/**
* @brief Handles ProxySQL message logging.
* @details The extra variadic arguments are expected to be received in the following order:
* ```
* proxy_error_func(msgid, fmt_message, time_buf, __FILE__, __LINE__, __func__ , ## __VA_ARGS__);
* ```
* Any other use of the function is UNSAFE. And will result in unespecified behavior.
* @param msgid The message id of the message to be logged, when non-zero, stats about the message are updated in
* 'umap_msg_stats'.
* @param fmt The formatted string to be pass to 'vfprintf'.
* @param ... The variadic list of arguments to be passed to 'vfprintf'.
*/
void proxy_error_func(int msgid, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
if (msgid != 0) {
va_list stats_params;
va_copy(stats_params, ap);
// ignore 'time' buffer argument
va_arg(stats_params, const char*);
// collect arguments '__FILE__', '__LINE__' and '__func__'
const char* file = va_arg(stats_params, const char*);
const int32_t line = va_arg(stats_params, int32_t);
const char* func = va_arg(stats_params, const char*);
std::lock_guard<std::mutex> msg_stats_guard { msg_stats_mutex };
string msg_stats_id { string { file } + ":" + std::to_string(line) + ":" + string { func } };
auto msg_stats = umap_msg_stats.find(msg_stats_id);
time_t tn = time(NULL);
const std::map<string,string> m_labels {
{ "message_id", std::to_string(msgid) }, { "filename", file },
{ "line", std::to_string(line) }, { "func", func }
};
prometheus::Family<prometheus::Counter>* m_family =
p_debug_dyn_counter_array[p_debug_dyn_counter::proxysql_message_count];
p_inc_map_counter(p_proxysql_messages_map, m_family, msg_stats_id, m_labels);
if (msg_stats != umap_msg_stats.end()) {
msg_stats->second.add_time(tn);
} else {
umap_msg_stats.insert(
{ msg_stats_id, ProxySQL_messages_stats(msgid, file, line, func, tn, tn, 1) }
);
}
}
vfprintf(stderr, fmt, ap);
va_end(ap);
};
SQLite3_result* proxysql_get_message_stats(bool reset) {
std::lock_guard<std::mutex> msg_stats_guard { msg_stats_mutex };
SQLite3_result* result = new SQLite3_result(ProxySQL_MSG_STATS_FIELD_NUM);
result->add_column_definition(SQLITE_TEXT,"message_id");
result->add_column_definition(SQLITE_TEXT,"filename");
result->add_column_definition(SQLITE_TEXT,"line");
result->add_column_definition(SQLITE_TEXT,"func");
result->add_column_definition(SQLITE_TEXT,"count_star");
result->add_column_definition(SQLITE_TEXT,"first_seen");
result->add_column_definition(SQLITE_TEXT,"last_seen");
for (const auto& msg_stats : umap_msg_stats) {
char** pta = msg_stats.second.get_row();
result->add_row(pta);
msg_stats.second.free_row(pta);
}
if (reset) {
umap_msg_stats.clear();
}
return result;
}
void proxysql_init_debug_prometheus_metrics() {
init_prometheus_dyn_counter_array<debug_metrics_map_idx, p_debug_dyn_counter>(debug_metrics_map, p_debug_dyn_counter_array);
}
static void full_write(int fd, const char *buf, size_t len)
{
while (len > 0) {
ssize_t ret = write(fd, buf, len);
if ((ret == -1) && (errno != EINTR))
break;
buf += (size_t) ret;
len -= (size_t) ret;
}
}
void print_backtrace(void)
{
static const char start[] = "BACKTRACE ------------\n";
static const char end[] = "----------------------\n";
void *bt[1024];
int bt_size;
char **bt_syms;
int i;
bt_size = backtrace(bt, 1024);
bt_syms = backtrace_symbols(bt, bt_size);
full_write(STDERR_FILENO, start, strlen(start));
for (i = 1; i < bt_size; i++) {
size_t len = strlen(bt_syms[i]);
full_write(STDERR_FILENO, bt_syms[i], len);
full_write(STDERR_FILENO, "\n", 1);
}
full_write(STDERR_FILENO, end, strlen(end));
free(bt_syms);
}
#ifdef DEBUG
void init_debug_struct() {
int i;
pthread_mutex_init(&debug_mutex,NULL);
pthread_rwlock_init(&filters_rwlock, NULL);
GloVars.global.gdbg_lvl= (debug_level *) malloc(PROXY_DEBUG_UNKNOWN*sizeof(debug_level));
for (i=0;i<PROXY_DEBUG_UNKNOWN;i++) {
GloVars.global.gdbg_lvl[i].module=(enum debug_module)i;
GloVars.global.gdbg_lvl[i].verbosity=( GloVars.global.gdbg ? INT_MAX : 0 );
GloVars.global.gdbg_lvl[i].name=(char *)NULL;
}
GloVars.global.gdbg_lvl[PROXY_DEBUG_GENERIC].name=(char *)"debug_generic";
GloVars.global.gdbg_lvl[PROXY_DEBUG_NET].name=(char *)"debug_net";
GloVars.global.gdbg_lvl[PROXY_DEBUG_PKT_ARRAY].name=(char *)"debug_pkt_array";
GloVars.global.gdbg_lvl[PROXY_DEBUG_POLL].name=(char *)"debug_poll";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_COM].name=(char *)"debug_mysql_com";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_SERVER].name=(char *)"debug_mysql_server";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_CONNECTION].name=(char *)"debug_mysql_connection";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_CONNPOOL].name=(char *)"debug_mysql_connpool";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_RW_SPLIT].name=(char *)"debug_mysql_rw_split";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_AUTH].name=(char *)"debug_mysql_auth";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_PROTOCOL].name=(char *)"debug_mysql_protocol";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_QUERY_PROCESSOR].name=(char *)"debug_mysql_query_processor";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MEMORY].name=(char *)"debug_memory";
GloVars.global.gdbg_lvl[PROXY_DEBUG_ADMIN].name=(char *)"debug_admin";
GloVars.global.gdbg_lvl[PROXY_DEBUG_SQLITE].name=(char *)"debug_sqlite";
GloVars.global.gdbg_lvl[PROXY_DEBUG_IPC].name=(char *)"debug_ipc";
GloVars.global.gdbg_lvl[PROXY_DEBUG_QUERY_CACHE].name=(char *)"debug_query_cache";
GloVars.global.gdbg_lvl[PROXY_DEBUG_QUERY_STATISTICS].name=(char *)"debug_query_statistics";
GloVars.global.gdbg_lvl[PROXY_DEBUG_RESTAPI].name=(char *)"debug_restapi";
GloVars.global.gdbg_lvl[PROXY_DEBUG_MONITOR].name=(char *)"debug_monitor";
GloVars.global.gdbg_lvl[PROXY_DEBUG_CLUSTER].name=(char *)"debug_cluster";
GloVars.global.gdbg_lvl[PROXY_DEBUG_GENAI].name=(char *)"debug_genai";
GloVars.global.gdbg_lvl[PROXY_DEBUG_NL2SQL].name=(char *)"debug_nl2sql";
GloVars.global.gdbg_lvl[PROXY_DEBUG_ANOMALY].name=(char *)"debug_anomaly";
for (i=0;i<PROXY_DEBUG_UNKNOWN;i++) {
// if this happen, the above table is not populated correctly
assert(GloVars.global.gdbg_lvl[i].name!=NULL);
}
}
void init_debug_struct_from_cmdline() {
if (GloVars.__cmd_proxysql_gdbg<0) return;
int i;
for (i=0;i<PROXY_DEBUG_UNKNOWN;i++) {
GloVars.global.gdbg_lvl[i].verbosity=GloVars.__cmd_proxysql_gdbg;
}
}
void proxysql_set_admin_debugdb_disk(SQLite3DB * _db) {
debugdb_disk = _db;
}
void proxysql_set_admin_debug_output(unsigned int _do) {
debug_output = _do;
}
#endif /* DEBUG */