diff --git a/Makefile b/Makefile index 023e13a..ac1225d 100644 --- a/Makefile +++ b/Makefile @@ -23,19 +23,19 @@ PG_VERSION := $(shell pg_config --version | awk {'print $$1 $$2'}) MAJOR := $(shell echo $(PG_VERSION) | sed -e 's/\.[^./]*$$//') ifneq (,$(findstring PostgreSQL14,$(MAJOR))) - CP := $(shell cp pg_stat_monitor--1.0.13.dat pg_stat_monitor--1.0.sql) + CP := $(shell cp pg_stat_monitor--1.0.14.sql.in pg_stat_monitor--1.0.sql) endif ifneq (,$(findstring PostgreSQL13,$(MAJOR))) - CP := $(shell cp pg_stat_monitor--1.0.13.dat pg_stat_monitor--1.0.sql) + CP := $(shell cp pg_stat_monitor--1.0.13.sql.in pg_stat_monitor--1.0.sql) endif ifneq (,$(findstring PostgreSQL12,$(MAJOR))) - CP := $(shell cp pg_stat_monitor--1.0.dat pg_stat_monitor--1.0.sql) + CP := $(shell cp pg_stat_monitor--1.0.sql.in pg_stat_monitor--1.0.sql) endif ifneq (,$(findstring PostgreSQL11,$(MAJOR))) - CP := $(shell cp pg_stat_monitor--1.0.dat pg_stat_monitor--1.0.sql) + CP := $(shell cp pg_stat_monitor--1.0.sql.in pg_stat_monitor--1.0.sql) endif ifdef USE_PGXS diff --git a/pg_stat_monitor--1.0.13.dat b/pg_stat_monitor--1.0.13.sql.in similarity index 99% rename from pg_stat_monitor--1.0.13.dat rename to pg_stat_monitor--1.0.13.sql.in index 768a5c5..e9e855e 100644 --- a/pg_stat_monitor--1.0.13.dat +++ b/pg_stat_monitor--1.0.13.sql.in @@ -82,7 +82,8 @@ CREATE FUNCTION pg_stat_monitor_internal(IN showtext boolean, OUT wal_records int8, OUT wal_fpi int8, OUT wal_bytes numeric, - OUT comments TEXT + OUT comments TEXT, + OUT toplevel BOOLEAN ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pg_stat_monitor' diff --git a/pg_stat_monitor--1.0.14.sql.in b/pg_stat_monitor--1.0.14.sql.in new file mode 100644 index 0000000..76ec2d5 --- /dev/null +++ b/pg_stat_monitor--1.0.14.sql.in @@ -0,0 +1,264 @@ +/* contrib/pg_stat_monitor/pg_stat_monitor--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_monitor" to load this file. \quit + +-- Register functions. +CREATE FUNCTION pg_stat_monitor_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_monitor_version() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION get_histogram_timings() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION range() +RETURNS text[] AS $$ +SELECT string_to_array(get_histogram_timings(), ','); +$$ LANGUAGE SQL; + +CREATE FUNCTION pg_stat_monitor_internal(IN showtext boolean, + OUT bucket int8, -- 0 + OUT userid oid, + OUT dbid oid, + OUT client_ip int8, + + OUT queryid text, -- 4 + OUT planid text, + OUT query text, + OUT query_plan text, + OUT state_code int8, + OUT top_queryid text, + OUT top_query text, + OUT application_name text, + + OUT relations text, -- 11 + OUT cmd_type int, + OUT elevel int, + OUT sqlcode TEXT, + OUT message text, + OUT bucket_start_time text, + + OUT calls int8, -- 16 + + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + + OUT rows_retrieved int8, + + OUT plans_calls int8, -- 23 + + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + + OUT shared_blks_hit int8, -- 29 + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT resp_calls text, -- 41 + OUT cpu_user_time float8, + OUT cpu_sys_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT comments TEXT, + OUT toplevel BOOLEAN +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_monitor' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION get_state(state_code int8) RETURNS TEXT AS +$$ +SELECT + CASE + WHEN state_code = 0 THEN 'PARSING' + WHEN state_code = 1 THEN 'PLANNING' + WHEN state_code = 2 THEN 'ACTIVE' + WHEN state_code = 3 THEN 'FINISHED' + WHEN state_code = 4 THEN 'FINISHED WITH ERROR' + END +$$ +LANGUAGE SQL PARALLEL SAFE; + +CREATE or REPLACE FUNCTION get_cmd_type (cmd_type INTEGER) RETURNS TEXT AS +$$ +SELECT + CASE + WHEN cmd_type = 0 THEN '' + WHEN cmd_type = 1 THEN 'SELECT' + WHEN cmd_type = 2 THEN 'UPDATE' + WHEN cmd_type = 3 THEN 'INSERT' + WHEN cmd_type = 4 THEN 'DELETE' + WHEN cmd_type = 5 THEN 'UTILITY' + WHEN cmd_type = 6 THEN 'NOTHING' + END +$$ +LANGUAGE SQL PARALLEL SAFE; + +CREATE FUNCTION pg_stat_monitor_settings( + OUT name text, + OUT value INTEGER, + OUT default_value INTEGER, + OUT description text, + OUT minimum INTEGER, + OUT maximum INTEGER, + OUT restart INTEGER +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_monitor_settings' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_monitor_settings AS SELECT + name, + value, + default_value, + description, + minimum, + maximum, + restart +FROM pg_stat_monitor_settings(); + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_monitor AS SELECT + bucket, + bucket_start_time AS bucket_start_time, + userid::regrole, + datname, + '0.0.0.0'::inet + client_ip AS client_ip, + queryid, + toplevel, + top_queryid, + query, + comments, + planid, + query_plan, + top_query, + application_name, + string_to_array(relations, ',') AS relations, + cmd_type, + get_cmd_type(cmd_type) AS cmd_type_text, + elevel, + sqlcode, + message, + calls, + total_exec_time, + min_exec_time, + max_exec_time, + mean_exec_time, + stddev_exec_time, + + rows_retrieved, + + plans_calls, + + total_plan_time, + min_plan_time, + max_plan_time, + mean_plan_time, + stddev_plan_time, + + shared_blks_hit, + shared_blks_read, + shared_blks_dirtied, + shared_blks_written, + local_blks_hit, + local_blks_read, + local_blks_dirtied, + local_blks_written, + temp_blks_read, + temp_blks_written, + blk_read_time, + blk_write_time, + (string_to_array(resp_calls, ',')) resp_calls, + cpu_user_time, + cpu_sys_time, + wal_records, + wal_fpi, + wal_bytes, + state_code, + get_state(state_code) as state +FROM pg_stat_monitor_internal(TRUE) p, pg_database d WHERE dbid = oid +ORDER BY bucket_start_time; + +CREATE FUNCTION decode_error_level(elevel int) +RETURNS text +AS +$$ +SELECT + CASE + WHEN elevel = 0 THEN '' + WHEN elevel = 10 THEN 'DEBUG5' + WHEN elevel = 11 THEN 'DEBUG4' + WHEN elevel = 12 THEN 'DEBUG3' + WHEN elevel = 13 THEN 'DEBUG2' + WHEN elevel = 14 THEN 'DEBUG1' + WHEN elevel = 15 THEN 'LOG' + WHEN elevel = 16 THEN 'LOG_SERVER_ONLY' + WHEN elevel = 17 THEN 'INFO' + WHEN elevel = 18 THEN 'NOTICE' + WHEN elevel = 19 THEN 'WARNING' + WHEN elevel = 20 THEN 'ERROR' + END +$$ +LANGUAGE SQL PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION histogram(_bucket int, _quryid text) +RETURNS SETOF RECORD AS $$ +DECLARE + rec record; +BEGIN +for rec in + with stat as (select queryid, bucket, unnest(range()) as range, unnest(resp_calls)::int freq from pg_stat_monitor) select range, freq, repeat('■', (freq::float / max(freq) over() * 30)::int) as bar from stat where queryid = _quryid and bucket = _bucket +loop +return next rec; +end loop; +END +$$ language plpgsql; + +CREATE FUNCTION pg_stat_monitor_hook_stats( + OUT hook text, + OUT min_time float8, + OUT max_time float8, + OUT total_time float8, + OUT ncalls int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_monitor_hook_stats' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_monitor_hook_stats AS SELECT + hook, + min_time, + max_time, + total_time, + total_time / greatest(ncalls, 1) as avg_time, + ncalls, + ROUND(CAST(total_time / greatest(sum(total_time) OVER(), 0.00000001) * 100 as numeric), 2)::text || '%' as load_comparison +FROM pg_stat_monitor_hook_stats(); + +GRANT SELECT ON pg_stat_monitor TO PUBLIC; +GRANT SELECT ON pg_stat_monitor_settings TO PUBLIC; +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_monitor_reset() FROM PUBLIC; diff --git a/pg_stat_monitor--1.0.dat b/pg_stat_monitor--1.0.sql.in similarity index 98% rename from pg_stat_monitor--1.0.dat rename to pg_stat_monitor--1.0.sql.in index 67cb3c2..544f668 100644 --- a/pg_stat_monitor--1.0.dat +++ b/pg_stat_monitor--1.0.sql.in @@ -79,8 +79,9 @@ CREATE FUNCTION pg_stat_monitor_internal(IN showtext boolean, OUT wal_records int8, OUT wal_fpi int8, OUT wal_bytes numeric, - OUT comments TEXT -) + OUT comments TEXT, + OUT toplevel BOOLEAN + ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pg_stat_monitor' LANGUAGE C STRICT VOLATILE PARALLEL SAFE; @@ -164,12 +165,6 @@ CREATE VIEW pg_stat_monitor AS SELECT mean_time, stddev_time, rows_retrieved, - plans_calls, - plan_total_time, - plan_min_time, - plan_max_time, - plan_mean_time, - shared_blks_hit, shared_blks_read, shared_blks_dirtied, diff --git a/pg_stat_monitor.c b/pg_stat_monitor.c index 1b68146..d7d49a1 100644 --- a/pg_stat_monitor.c +++ b/pg_stat_monitor.c @@ -1499,6 +1499,7 @@ pgss_store(uint64 queryid, key.ip = pg_get_client_addr(); key.planid = planid; key.appid = appid; + key.toplevel = (nested_level == 0); pgss_hash = pgsm_get_hash(); @@ -1664,7 +1665,7 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "pg_stat_monitor: return type must be a row type"); - if (tupdesc->natts != 50) + if (tupdesc->natts != 51) elog(ERROR, "pg_stat_monitor: incorrect number of output arguments, required %d", tupdesc->natts); tupstore = tuplestore_begin_heap(true, false, work_mem); @@ -1694,9 +1695,11 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, uint64 planid = entry->key.planid; unsigned char *buf = pgss_qbuf[bucketid]; #if PG_VERSION_NUM < 140000 + bool toplevel = true; bool is_allowed_role = is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS); #else bool is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS); + bool toplevel = entry->key.toplevel; #endif if (read_query(buf, queryid, query_txt, entry->query_pos) == 0) @@ -1980,6 +1983,7 @@ pg_stat_monitor_internal(FunctionCallInfo fcinfo, else nulls[i++] = true; } + values[i++] = BoolGetDatum(toplevel); tuplestore_putvalues(tupstore, tupdesc, values, nulls); } pfree(query_txt); diff --git a/pg_stat_monitor.h b/pg_stat_monitor.h index be32103..04b5c52 100644 --- a/pg_stat_monitor.h +++ b/pg_stat_monitor.h @@ -203,6 +203,7 @@ typedef struct pgssHashKey uint64 ip; /* client ip address */ uint64 planid; /* plan identifier */ uint64 appid; /* hash of application name */ + bool toplevel; /* query executed at top level */ } pgssHashKey; typedef struct QueryInfo diff --git a/regression/expected/guc.out b/regression/expected/guc.out index ef3da0b..934c719 100644 --- a/regression/expected/guc.out +++ b/regression/expected/guc.out @@ -23,11 +23,12 @@ SELECT * FROM pg_stat_monitor_settings ORDER BY name COLLATE "C"; pg_stat_monitor.pgsm_max | 100 | 100 | Sets the maximum size of shared memory in (MB) used for statement's metadata tracked by pg_stat_monitor. | 1 | 1000 | 1 pg_stat_monitor.pgsm_max_buckets | 10 | 10 | Sets the maximum number of buckets. | 1 | 10 | 1 pg_stat_monitor.pgsm_normalized_query | 1 | 1 | Selects whether save query in normalized format. | 0 | 0 | 0 - pg_stat_monitor.pgsm_overflow_target | 1 | 1 | Sets the overflow target for pg_stat_monitor | 0 | 1 | 1 + pg_stat_monitor.pgsm_overflow_target | 0 | 1 | Sets the overflow target for pg_stat_monitor | 0 | 1 | 1 pg_stat_monitor.pgsm_query_max_len | 1024 | 1024 | Sets the maximum length of query. | 1024 | 2147483647 | 1 pg_stat_monitor.pgsm_query_shared_buffer | 20 | 20 | Sets the maximum size of shared memory in (MB) used for query tracked by pg_stat_monitor. | 1 | 10000 | 1 + pg_stat_monitor.pgsm_track_planning | 1 | 1 | Selects whether planning statistics are tracked. | 0 | 0 | 0 pg_stat_monitor.pgsm_track_utility | 1 | 1 | Selects whether utility commands are tracked. | 0 | 0 | 0 -(13 rows) +(14 rows) SELECT pg_stat_monitor_reset(); pg_stat_monitor_reset