PG-293: Add pg_stat_monitor.track GUC.

This new GUC allows users to select which statements are tracked by
pg_stat_monitor:
   - 'top': Default, track only top level queries.
   - 'all': Track top along with sub/nested queries.
   - 'none': Disable query monitoring.

To avoid redudancy, now users disable pg_stat_monitor by setting
pg_stat_monitor.track = 'none', similar to pg_stat_statements.

This new GUC is an enumeration, so the pg_stat_monitor_settings view was
adjusted to add a new column 'options' which lists the possible values
for the field.

The "value" and "default_value" columns in the pg_stat_monitor_settings
view was adjusted to be of type text, so we can better display the
enumeration values. Also the boolean types now have their values
displayed as either 'yes' or 'no' to easily distinguish them from the
integer types.
pull/157/head
Diego Fronza 2021-12-09 14:49:35 -03:00
parent 6f7f44b744
commit b6838049b6
4 changed files with 188 additions and 68 deletions

50
guc.c
View File

@ -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];
}

View File

@ -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();

View File

@ -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 */

View File

@ -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
/*