From 130d6b5fcedcaa49d0444dedd4ae35a91e7b28cb Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 6 Aug 2024 22:43:48 +0100 Subject: [PATCH] PG-592: Treat queries with different parent queries as separate entries (#403) * PG-592: Treat queries with different parent queries as separate entries 1. Previously pg_stat_monitor had a `topquery` and `topqueryid` field, but it was only a sample: it showed one of the top queries executing the specific query. With this change, the same entry executed by two different functions will result in two entries in the statistics table. 2. This also fixes a bug where the content of these field disappeared for every second query executed: previously the update function changed topqueryid to `0` if it was non zero, and changed it to the proper id when it was 0. This resulted in an alternating behavior, where for every second executed query the top query disappeared. After these changes, the top query is always shown. 3. The previous implementation also leaked dsa memory used to store the parent queries. This is now also fixed. * PG-502: Fixing review comments * dsa_free changed to assert as it can never happen * restructured the ifs to be cleaner Note: kept the two-level ifs, as that makes more sense with the assert Note: didn't convert nested_level checks to macro, as it is used differently at different parts of the code * PG-502: Fixing review comments * PG-592 Add regression test * Make test compatible with PG12 * Remove redundant line --------- Co-authored-by: Artem Gavrilov --- .gitignore | 1 + Makefile | 2 +- pg_stat_monitor.c | 34 ++++++++---- pg_stat_monitor.h | 2 +- .../expected/different_parent_queries.out | 54 +++++++++++++++++++ regression/sql/different_parent_queries.sql | 24 +++++++++ 6 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 regression/expected/different_parent_queries.out create mode 100644 regression/sql/different_parent_queries.sql diff --git a/.gitignore b/.gitignore index 3ba025a8..299caa8d 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ *.mod* *.cmd .tmp_versions/ +.deps/ modules.order Module.symvers Mkfile.old diff --git a/Makefile b/Makefile index 117c4871..ba11b1ec 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ LDFLAGS_SL += $(filter -lm, $(LIBS)) TAP_TESTS = 1 REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_monitor/pg_stat_monitor.conf --inputdir=regression -REGRESS = basic version guc pgsm_query_id functions counters relations database error_insert application_name application_name_unique top_query cmd_type error rows tags user level_tracking +REGRESS = basic version guc pgsm_query_id functions counters relations database error_insert application_name application_name_unique top_query different_parent_queries cmd_type error rows tags user level_tracking # Disabled because these tests require "shared_preload_libraries=pg_stat_statements", # which typical installcheck users do not have (e.g. buildfarm clients). diff --git a/pg_stat_monitor.c b/pg_stat_monitor.c index 91f1a2b4..b6642e91 100644 --- a/pg_stat_monitor.c +++ b/pg_stat_monitor.c @@ -1542,15 +1542,13 @@ pgsm_update_entry(pgsmEntry * entry, e->counters.info.num_relations = num_relations; _snprintf2(e->counters.info.relations, relations, num_relations, REL_LEN); - if (nesting_level > 0 && e->counters.info.parentid == 0 && pgsm_track == PGSM_TRACK_ALL) + if (nesting_level > 0 && nesting_level < max_stack_depth && e->key.parentid != 0 && pgsm_track == PGSM_TRACK_ALL) { - if (nesting_level >= 0 && nesting_level < max_stack_depth) + if (!DsaPointerIsValid(e->counters.info.parent_query)) { int parent_query_len = nested_query_txts[nesting_level - 1] ? strlen(nested_query_txts[nesting_level - 1]) : 0; - e->counters.info.parentid = nested_queryids[nesting_level - 1]; - e->counters.info.parent_query = InvalidDsaPointer; /* If we have a parent query, store it in the raw dsa area */ if (parent_query_len > 0) { @@ -1577,8 +1575,7 @@ pgsm_update_entry(pgsmEntry * entry, } else { - e->counters.info.parentid = UINT64CONST(0); - e->counters.info.parent_query = InvalidDsaPointer; + Assert(!DsaPointerIsValid(e->counters.info.parent_query)); } } @@ -1826,6 +1823,7 @@ pgsm_create_hash_entry(uint64 bucket_id, uint64 queryid, PlanInfo * plan_info) entry->key.dbid = MyDatabaseId; entry->key.queryid = queryid; entry->key.bucket_id = bucket_id; + entry->key.parentid = 0; #if PG_VERSION_NUM < 140000 entry->key.toplevel = 1; @@ -1947,13 +1945,24 @@ pgsm_store(pgsmEntry * entry) memcpy(&jitusage.optimization_counter, &entry->counters.jitinfo.instr_optimization_counter, sizeof(instr_time)); memcpy(&jitusage.emission_counter, &entry->counters.jitinfo.instr_emission_counter, sizeof(instr_time)); + + // Update parent id if needed + if(pgsm_track == PGSM_TRACK_ALL && nesting_level > 0 && nesting_level < max_stack_depth) + { + entry->key.parentid = nested_queryids[nesting_level - 1]; + } + else + { + entry->key.parentid = UINT64CONST(0); + } + #if PG_VERSION_NUM >= 170000 memcpy(&jitusage.deform_counter, &entry->counters.jitinfo.instr_deform_counter, sizeof(instr_time)); #endif /* * Acquire a share lock to start with. We'd have to acquire exclusive if - * we need ot create the entry. + * we need to create the entry. */ LWLockAcquire(pgsm->lock, LW_SHARED); shared_hash_entry = (pgsmEntry *) pgsm_hash_find(get_pgsmHash(), &entry->key, &found); @@ -2047,6 +2056,7 @@ pgsm_store(pgsmEntry * entry) shared_hash_entry->pgsm_query_id = entry->pgsm_query_id; shared_hash_entry->encoding = entry->encoding; shared_hash_entry->counters.info.cmd_type = entry->counters.info.cmd_type; + shared_hash_entry->counters.info.parent_query = InvalidDsaPointer; snprintf(shared_hash_entry->datname, sizeof(shared_hash_entry->datname), "%s", entry->datname); snprintf(shared_hash_entry->username, sizeof(shared_hash_entry->username), "%s", entry->username); @@ -2233,6 +2243,7 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, bool nulls[PG_STAT_MONITOR_COLS] = {0}; int i = 0; Counters tmp; + pgsmHashKey tmpkey; double stddev; uint64 queryid = entry->key.queryid; int64 bucketid = entry->key.bucket_id; @@ -2269,6 +2280,7 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, SpinLockAcquire(&e->mutex); tmp = e->counters; + tmpkey = e->key; SpinLockRelease(&e->mutex); } @@ -2285,7 +2297,7 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, } /* read the parent query text if any */ - if (tmp.info.parentid != UINT64CONST(0)) + if (tmpkey.parentid != UINT64CONST(0)) { if (DsaPointerIsValid(tmp.info.parent_query)) { @@ -2370,10 +2382,10 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, else nulls[i++] = true; - /* parentid at column number 11 */ - if (tmp.info.parentid != UINT64CONST(0)) + /* parentid at column number 9 */ + if (tmpkey.parentid != UINT64CONST(0)) { - values[i++] = UInt64GetDatum(tmp.info.parentid); + values[i++] = UInt64GetDatum(tmpkey.parentid); values[i++] = CStringGetTextDatum(parent_query_txt); } else diff --git a/pg_stat_monitor.h b/pg_stat_monitor.h index c4a23d5d..32339da9 100644 --- a/pg_stat_monitor.h +++ b/pg_stat_monitor.h @@ -240,11 +240,11 @@ typedef struct pgsmHashKey Oid dbid; /* database OID */ uint32 ip; /* client ip address */ bool toplevel; /* query executed at top level */ + uint64 parentid; /* parent queryid of current query */ } pgsmHashKey; typedef struct QueryInfo { - uint64 parentid; /* parent queryid of current query */ dsa_pointer parent_query; int64 type; /* type of query, options are query, info, * warning, error, fatal */ diff --git a/regression/expected/different_parent_queries.out b/regression/expected/different_parent_queries.out new file mode 100644 index 00000000..ff0a674b --- /dev/null +++ b/regression/expected/different_parent_queries.out @@ -0,0 +1,54 @@ +CREATE EXTENSION pg_stat_monitor; +SET pg_stat_monitor.pgsm_track='all'; +SELECT pg_stat_monitor_reset(); + pg_stat_monitor_reset +----------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION test() RETURNS VOID AS +$$ +BEGIN + PERFORM 1 + 2; +END; $$ language plpgsql; +CREATE OR REPLACE FUNCTION test2() RETURNS VOID AS +$$ +BEGIN + PERFORM 1 + 2; +END; $$ language plpgsql; +SELECT pg_stat_monitor_reset(); + pg_stat_monitor_reset +----------------------- + +(1 row) + +SELECT test(); + test +------ + +(1 row) + +SELECT test2(); + test2 +------- + +(1 row) + +SELECT 1 + 2; + ?column? +---------- + 3 +(1 row) + +SELECT left(query, 15) as query, calls, top_query, pgsm_query_id FROM pg_stat_monitor ORDER BY query, top_query COLLATE "C"; + query | calls | top_query | pgsm_query_id +-----------------+-------+-----------------+---------------------- + SELECT 1 + 2 | 1 | SELECT test(); | 5193804135051352284 + SELECT 1 + 2 | 1 | SELECT test2(); | 5193804135051352284 + SELECT 1 + 2 | 1 | | 5193804135051352284 + SELECT pg_stat_ | 1 | | 689150021118383254 + SELECT test() | 1 | | -6801876889834540522 + SELECT test2() | 1 | | 369102705908374543 +(6 rows) + +DROP EXTENSION pg_stat_monitor; diff --git a/regression/sql/different_parent_queries.sql b/regression/sql/different_parent_queries.sql new file mode 100644 index 00000000..c3e1d180 --- /dev/null +++ b/regression/sql/different_parent_queries.sql @@ -0,0 +1,24 @@ +CREATE EXTENSION pg_stat_monitor; +SET pg_stat_monitor.pgsm_track='all'; +SELECT pg_stat_monitor_reset(); + +CREATE OR REPLACE FUNCTION test() RETURNS VOID AS +$$ +BEGIN + PERFORM 1 + 2; +END; $$ language plpgsql; + +CREATE OR REPLACE FUNCTION test2() RETURNS VOID AS +$$ +BEGIN + PERFORM 1 + 2; +END; $$ language plpgsql; + +SELECT pg_stat_monitor_reset(); +SELECT test(); +SELECT test2(); + +SELECT 1 + 2; +SELECT left(query, 15) as query, calls, top_query, pgsm_query_id FROM pg_stat_monitor ORDER BY query, top_query COLLATE "C"; + +DROP EXTENSION pg_stat_monitor; \ No newline at end of file