diff --git a/guc.c b/guc.c index 52db4e3..528318f 100644 --- a/guc.c +++ b/guc.c @@ -21,6 +21,7 @@ GucVariable conf[MAX_SETTINGS]; static void DefineIntGUC(GucVariable *conf); static void DefineBoolGUC(GucVariable *conf); +static void DefineEnumGUC(GucVariable *conf, const struct config_enum_entry *options); /* * Define (or redefine) custom GUC variables. @@ -29,6 +30,7 @@ void init_guc(void) { int i = 0; + conf[i] = (GucVariable) { .guc_name = "pg_stat_monitor.pgsm_max", .guc_desc = "Sets the maximum size of shared memory in (MB) used for statement's metadata tracked by pg_stat_monitor.", @@ -53,18 +55,6 @@ init_guc(void) }; DefineIntGUC(&conf[i++]); - conf[i] = (GucVariable) { - .guc_name = "pg_stat_monitor.pgsm_enable", - .guc_desc = "Enable/Disable statistics collector.", - .guc_default = 1, - .guc_min = 0, - .guc_max = 0, - .guc_restart = false, - .guc_unit = 0, - .guc_value = &PGSM_ENABLED - }; - DefineBoolGUC(&conf[i++]); - conf[i] = (GucVariable) { .guc_name = "pg_stat_monitor.pgsm_track_utility", .guc_desc = "Selects whether utility commands are tracked.", @@ -185,6 +175,21 @@ init_guc(void) }; DefineBoolGUC(&conf[i++]); + conf[i] = (GucVariable) { + .guc_name = "pg_stat_monitor.track", + .guc_desc = "Selects which statements are tracked by pg_stat_monitor.", + .n_options = 3, + .guc_default = PGSM_TRACK_TOP, + .guc_min = PSGM_TRACK_NONE, + .guc_max = PGSM_TRACK_ALL, + .guc_restart = false, + .guc_unit = 0, + .guc_value = &PGSM_TRACK + }; + for (int j = 0; j < conf[i].n_options; ++j) { + strlcpy(conf[i].guc_options[j], track_options[j].name, sizeof(conf[i].guc_options[j])); + } + DefineEnumGUC(&conf[i++], track_options); #if PG_VERSION_NUM >= 130000 conf[i] = (GucVariable) { @@ -204,6 +209,7 @@ init_guc(void) static void DefineIntGUC(GucVariable *conf) { + conf->type = PGC_INT; DefineCustomIntVariable(conf->guc_name, conf->guc_desc, NULL, @@ -220,6 +226,7 @@ DefineIntGUC(GucVariable *conf) static void DefineBoolGUC(GucVariable *conf) { + conf->type = PGC_BOOL; DefineCustomBoolVariable(conf->guc_name, conf->guc_desc, NULL, @@ -232,9 +239,26 @@ DefineBoolGUC(GucVariable *conf) NULL); } +static void +DefineEnumGUC(GucVariable *conf, const struct config_enum_entry *options) +{ + conf->type = PGC_ENUM; + DefineCustomEnumVariable(conf->guc_name, + conf->guc_desc, + NULL, + conf->guc_value, + PGSM_TRACK_TOP, + options, + conf->guc_restart ? PGC_POSTMASTER : PGC_USERSET, + 0, + NULL, + NULL, + NULL); +} + GucVariable* get_conf(int i) { - return &conf[i]; + return &conf[i]; } diff --git a/pg_stat_monitor--1.0.sql.in b/pg_stat_monitor--1.0.sql.in index 106106d..8ecbc92 100644 --- a/pg_stat_monitor--1.0.sql.in +++ b/pg_stat_monitor--1.0.sql.in @@ -116,12 +116,13 @@ LANGUAGE SQL PARALLEL SAFE; CREATE FUNCTION pg_stat_monitor_settings( OUT name text, - OUT value INTEGER, - OUT default_value INTEGER, + OUT value text, + OUT default_value text, OUT description text, OUT minimum INTEGER, OUT maximum INTEGER, - OUT restart INTEGER + OUT options text, + OUT restart text ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pg_stat_monitor_settings' @@ -134,6 +135,7 @@ CREATE VIEW pg_stat_monitor_settings AS SELECT description, minimum, maximum, + options, restart FROM pg_stat_monitor_settings(); diff --git a/pg_stat_monitor.c b/pg_stat_monitor.c index 0b93662..4e33ad8 100644 --- a/pg_stat_monitor.c +++ b/pg_stat_monitor.c @@ -46,6 +46,12 @@ do \ strlcpy((char *)_str_dst[i], _str_src[i], _len2); \ }while(0) +#define pgsm_enabled(level) \ + (!IsParallelWorker() && \ + (PGSM_TRACK == PGSM_TRACK_ALL || \ + (PGSM_TRACK == PGSM_TRACK_TOP && (level) == 0))) + + /*---- Initicalization Function Declarations ----*/ void _PG_init(void); void _PG_fini(void); @@ -53,7 +59,7 @@ void _PG_fini(void); /*---- Local variables ----*/ /* Current nesting depth of ExecutorRun+ProcessUtility calls */ -static int nested_level = 0; +static int exec_nested_level = 0; #if PG_VERSION_NUM >= 130000 static int plan_nested_level = 0; #endif @@ -356,7 +362,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) if (!IsSystemInitialized()) return; - if (IsParallelWorker()) + if (!pgsm_enabled(exec_nested_level)) return; /* @@ -421,7 +427,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query) if (!IsSystemInitialized()) return; - if (IsParallelWorker()) + if (!pgsm_enabled(exec_nested_level)) return; /* @@ -489,15 +495,13 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) else standard_ExecutorStart(queryDesc, eflags); - if (IsParallelWorker()) - return; - /* * If query has queryId zero, don't track it. This prevents double * counting of optimizable statements that are directly contained in * utility statements. */ - if (PGSM_ENABLED && queryDesc->plannedstmt->queryId != UINT64CONST(0)) + if (pgsm_enabled(exec_nested_level) && + queryDesc->plannedstmt->queryId != UINT64CONST(0)) { /* * Set up to track total elapsed time in ExecutorRun. Make sure the @@ -552,24 +556,24 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once) { - if (nested_level >=0 && nested_level < max_stack_depth) - nested_queryids[nested_level] = queryDesc->plannedstmt->queryId; - nested_level++; + if (exec_nested_level >=0 && exec_nested_level < max_stack_depth) + nested_queryids[exec_nested_level] = queryDesc->plannedstmt->queryId; + exec_nested_level++; PG_TRY(); { if (prev_ExecutorRun) prev_ExecutorRun(queryDesc, direction, count, execute_once); else standard_ExecutorRun(queryDesc, direction, count, execute_once); - nested_level--; - if (nested_level >=0 && nested_level < max_stack_depth) - nested_queryids[nested_level] = UINT64CONST(0); + exec_nested_level--; + if (exec_nested_level >=0 && exec_nested_level < max_stack_depth) + nested_queryids[exec_nested_level] = UINT64CONST(0); } PG_CATCH(); { - nested_level--; - if (nested_level >=0 && nested_level < max_stack_depth) - nested_queryids[nested_level] = UINT64CONST(0); + exec_nested_level--; + if (exec_nested_level >=0 && exec_nested_level < max_stack_depth) + nested_queryids[exec_nested_level] = UINT64CONST(0); PG_RE_THROW(); } PG_END_TRY(); @@ -592,18 +596,18 @@ pgss_ExecutorFinish_benchmark(QueryDesc *queryDesc) static void pgss_ExecutorFinish(QueryDesc *queryDesc) { - nested_level++; + exec_nested_level++; PG_TRY(); { if (prev_ExecutorFinish) prev_ExecutorFinish(queryDesc); else standard_ExecutorFinish(queryDesc); - nested_level--; + exec_nested_level--; } PG_CATCH(); { - nested_level--; + exec_nested_level--; PG_RE_THROW(); } PG_END_TRY(); @@ -661,7 +665,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) MemoryContextSwitchTo(mct); } - if (queryId != UINT64CONST(0) && queryDesc->totaltime && !IsParallelWorker()) + if (queryId != UINT64CONST(0) && queryDesc->totaltime && pgsm_enabled(exec_nested_level)) { /* * Make sure stats accumulation is done. (Note: it's okay if several @@ -778,7 +782,20 @@ pgss_planner_hook(Query *parse, const char *query_string, int cursorOptions, Par { PlannedStmt *result; - if (PGSM_TRACK_PLANNING && query_string && parse->queryId != UINT64CONST(0) && !IsParallelWorker()) + /* + * We can't process the query if no query_string is provided, as + * pgss_store needs it. We also ignore query without queryid, as it would + * be treated as a utility statement, which may not be the case. + * + * Note that planner_hook can be called from the planner itself, so we + * have a specific nesting level for the planner. However, utility + * commands containing optimizable statements can also call the planner, + * same for regular DML (for instance for underlying foreign key queries). + * So testing the planner nesting level only is not enough to detect real + * top level planner call. + */ + if (pgsm_enabled(plan_nested_level + exec_nested_level) && + PGSM_TRACK_PLANNING && query_string && parse->queryId != UINT64CONST(0)) { instr_time start; instr_time duration; @@ -946,7 +963,7 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, * since we are already measuring the statement's costs at the utility * level. */ - if (PGSM_TRACK_UTILITY && !IsParallelWorker()) + if (PGSM_TRACK_UTILITY && pgsm_enabled(exec_nested_level)) pstmt->queryId = UINT64CONST(0); #endif @@ -964,8 +981,8 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, * * Likewise, we don't track execution of DEALLOCATE. */ - if (PGSM_TRACK_UTILITY && PGSM_HANDLED_UTILITY(parsetree) && - !IsParallelWorker()) + if (PGSM_TRACK_UTILITY && pgsm_enabled(exec_nested_level) && + PGSM_HANDLED_UTILITY(parsetree)) { instr_time start; instr_time duration; @@ -1018,7 +1035,7 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, } PG_CATCH(); { - nested_level--; + exec_nested_level--; PG_RE_THROW(); } @@ -1310,10 +1327,10 @@ pgss_update_entry(pgssEntry *entry, e->counters.info.cmd_type = cmd_type; - if(nested_level > 0) + if(exec_nested_level > 0) { - if (nested_level >=0 && nested_level < max_stack_depth) - e->counters.info.parentid = nested_queryids[nested_level - 1]; + if (exec_nested_level >=0 && exec_nested_level < max_stack_depth) + e->counters.info.parentid = nested_queryids[exec_nested_level - 1]; } else { @@ -1429,10 +1446,6 @@ pgss_store(uint64 queryid, static bool found_client_addr = false; static uint client_addr = 0; - /* Monitoring is disabled */ - if (!PGSM_ENABLED) - return; - /* Safety check... */ if (!IsSystemInitialized()) return; @@ -3315,7 +3328,7 @@ pg_stat_monitor_settings(PG_FUNCTION_ARGS) return (Datum) 0; } - if (tupdesc->natts != 7) + if (tupdesc->natts != 8) { pgsm_log_error("pg_stat_monitor_settings: incorrect number of output arguments, required: 7, found %d", tupdesc->natts); return (Datum) 0; @@ -3330,19 +3343,78 @@ pg_stat_monitor_settings(PG_FUNCTION_ARGS) for(i = 0; i < MAX_SETTINGS; i++) { - Datum values[7]; - bool nulls[7]; + Datum values[8]; + bool nulls[8]; int j = 0; + char options[1024] = ""; + GucVariable *conf; + memset(values, 0, sizeof(values)); memset(nulls, 0, sizeof(nulls)); - values[j++] = CStringGetTextDatum(get_conf(i)->guc_name); - values[j++] = Int64GetDatumFast(get_conf(i)->guc_variable); - values[j++] = Int64GetDatumFast(get_conf(i)->guc_default); + conf = get_conf(i); + + values[j++] = CStringGetTextDatum(conf->guc_name); + + /* Handle current and default values. */ + switch (conf->type) + { + case PGC_ENUM: + values[j++] = CStringGetTextDatum(conf->guc_options[conf->guc_variable]); + values[j++] = CStringGetTextDatum(conf->guc_options[conf->guc_default]); + break; + + case PGC_INT: + { + char value[32]; + + sprintf(value, "%d", conf->guc_variable); + values[j++] = CStringGetTextDatum(value); + + sprintf(value, "%d", conf->guc_default); + values[j++] = CStringGetTextDatum(value); + break; + } + + case PGC_BOOL: + values[j++] = CStringGetTextDatum(conf->guc_variable ? "yes" : "no"); + values[j++] = CStringGetTextDatum(conf->guc_default ? "yes" : "no"); + break; + + default: + Assert(false); + } + values[j++] = CStringGetTextDatum(get_conf(i)->guc_desc); - values[j++] = Int64GetDatumFast(get_conf(i)->guc_min); - values[j++] = Int64GetDatumFast(get_conf(i)->guc_max); - values[j++] = Int64GetDatumFast(get_conf(i)->guc_restart); + + /* Minimum and maximum displayed only for integers or real numbers. */ + if (conf->type != PGC_INT) + { + nulls[j++] = true; + nulls[j++] = true; + } + else + { + values[j++] = Int64GetDatumFast(get_conf(i)->guc_min); + values[j++] = Int64GetDatumFast(get_conf(i)->guc_max); + } + + if (conf->type == PGC_ENUM) + { + strcat(options, conf->guc_options[0]); + for (size_t i = 1; i < conf->n_options; ++i) + { + strcat(options, ", "); + strcat(options, conf->guc_options[i]); + } + } + else if (conf->type == PGC_BOOL) + { + strcat(options, "yes, no"); + } + + values[j++] = CStringGetTextDatum(options); + values[j++] = CStringGetTextDatum(get_conf(i)->guc_restart ? "yes" : "no"); tuplestore_putvalues(tupstore, tupdesc, values, nulls); } /* clean up and return the tuplestore */ diff --git a/pg_stat_monitor.h b/pg_stat_monitor.h index 648aa88..eedc702 100644 --- a/pg_stat_monitor.h +++ b/pg_stat_monitor.h @@ -52,6 +52,7 @@ #include "utils/timestamp.h" #include "utils/lsyscache.h" #include "utils/guc.h" +#include "utils/guc_tables.h" #define MAX_BACKEND_PROCESES (MaxBackends + NUM_AUXILIARY_PROCS + max_prepared_xacts) #define IntArrayGetTextDatum(x,y) intarray_get_datum(x,y) @@ -99,8 +100,11 @@ #define MAX_SETTINGS 13 #endif +/* Update this if need a enum GUC with more options. */ +#define MAX_ENUM_OPTIONS 6 typedef struct GucVariables { + enum config_type type; /* PGC_BOOL, PGC_INT, PGC_REAL, PGC_STRING, PGC_ENUM */ int guc_variable; char guc_name[TEXT_LEN]; char guc_desc[TEXT_LEN]; @@ -110,6 +114,8 @@ typedef struct GucVariables int guc_unit; int *guc_value; bool guc_restart; + int n_options; + char guc_options[MAX_ENUM_OPTIONS][32]; } GucVariable; #if PG_VERSION_NUM < 130000 @@ -408,22 +414,38 @@ void set_qbuf(unsigned char *); /* hash_query.c */ void pgss_startup(void); + /*---- GUC variables ----*/ +typedef enum { + PSGM_TRACK_NONE, /* track no statements */ + PGSM_TRACK_TOP, /* only top level statements */ + PGSM_TRACK_ALL /* all statements, including nested ones */ +} PGSMTrackLevel; + +static const struct config_enum_entry track_options[] = +{ + {"none", PSGM_TRACK_NONE, false}, + {"top", PGSM_TRACK_TOP, false}, + {"all", PGSM_TRACK_ALL, false}, + {NULL, 0, false} +}; + #define PGSM_MAX get_conf(0)->guc_variable #define PGSM_QUERY_MAX_LEN get_conf(1)->guc_variable -#define PGSM_ENABLED get_conf(2)->guc_variable -#define PGSM_TRACK_UTILITY get_conf(3)->guc_variable -#define PGSM_NORMALIZED_QUERY get_conf(4)->guc_variable -#define PGSM_MAX_BUCKETS get_conf(5)->guc_variable -#define PGSM_BUCKET_TIME get_conf(6)->guc_variable -#define PGSM_HISTOGRAM_MIN get_conf(7)->guc_variable -#define PGSM_HISTOGRAM_MAX get_conf(8)->guc_variable -#define PGSM_HISTOGRAM_BUCKETS get_conf(9)->guc_variable -#define PGSM_QUERY_SHARED_BUFFER get_conf(10)->guc_variable -#define PGSM_OVERFLOW_TARGET get_conf(11)->guc_variable -#define PGSM_QUERY_PLAN get_conf(12)->guc_variable +#define PGSM_TRACK_UTILITY get_conf(2)->guc_variable +#define PGSM_NORMALIZED_QUERY get_conf(3)->guc_variable +#define PGSM_MAX_BUCKETS get_conf(4)->guc_variable +#define PGSM_BUCKET_TIME get_conf(5)->guc_variable +#define PGSM_HISTOGRAM_MIN get_conf(6)->guc_variable +#define PGSM_HISTOGRAM_MAX get_conf(7)->guc_variable +#define PGSM_HISTOGRAM_BUCKETS get_conf(8)->guc_variable +#define PGSM_QUERY_SHARED_BUFFER get_conf(9)->guc_variable +#define PGSM_OVERFLOW_TARGET get_conf(10)->guc_variable +#define PGSM_QUERY_PLAN get_conf(11)->guc_variable +#define PGSM_TRACK get_conf(12)->guc_variable #define PGSM_TRACK_PLANNING get_conf(13)->guc_variable + /*---- Benchmarking ----*/ #ifdef BENCHMARK /*