Skip to content

Commit

Permalink
get_parsedinfo + pg13
Browse files Browse the repository at this point in the history
  • Loading branch information
bdt committed Oct 6, 2020
1 parent 1ca3825 commit b124855
Show file tree
Hide file tree
Showing 8 changed files with 774 additions and 333 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
@@ -0,0 +1,14 @@
root = true

[*.{c,h,l,y,pl,pm}]
indent_style = tab
indent_size = tab
tab_width = 4

[*.{sgml,xml}]
indent_style = space
indent_size = 1

[*.xsl]
indent_style = space
indent_size = 2
23 changes: 12 additions & 11 deletions README.md
Expand Up @@ -6,7 +6,7 @@
Introduction
------------

PostgreSQL provides session activity. However, in order to gather activity
PostgreSQL provides session activity. However, in order to gather activity
behavior user have to sample the pg_stat_activity view multiple times.
`pgsentinel` is an extension to record active session history and also link
the activity with query statistics (`pg_stat_statements`).
Expand Down Expand Up @@ -85,6 +85,7 @@ Usage
| datid | oid | | | |
| datname | text | | | |
| pid | integer | | | |
| leader_pid | integer | | | |
| usesysid | oid | | | |
| usename | text | | | |
| application_name | text | | | |
Expand Down Expand Up @@ -130,7 +131,7 @@ You could see it as samplings of `pg_stat_activity` providing more information:
| dbid | oid | | | |
| queryid | bigint | | | |
| calls | bigint | | | |
| total_time | double precision | | | |
| total_exec_time | double precision | | | |
| rows | bigint | | | |
| shared_blks_hit | bigint | | | |
| shared_blks_read | bigint | | | |
Expand All @@ -144,8 +145,13 @@ You could see it as samplings of `pg_stat_activity` providing more information:
| temp_blks_written | bigint | | | |
| blk_read_time | double precision | | | |
| blk_write_time | double precision | | | |
| plans | bigint | | | |
| total_plan_time | double precision | | | |
| wal_records | bigint | | | |
| wal_fpi | bigint | | | |
| wal_bytes | numeric | | | |

The fields description are the same as for `pg_stat_statements` (except for the `ash_time` one, which is the time of the active session history sampling)
The fields description are the same as for `pg_stat_statements` (except for the `ash_time` one, which is the time of the active session history sampling).

The worker is controlled by the following GUCs:

Expand All @@ -157,11 +163,10 @@ The worker is controlled by the following GUCs:
| pgsentinel_pgssh.max_entries | int4 | Size of pg_stat_statements_history in-memory ring buffer | 1000 | 1000 |
| pgsentinel_pgssh.enable | boolean | enable pg_stat_statements_history | false | |

Remarks for PostgreSQL 9.6
Remark
-------------------------

* The `backend_type` field does not exist into the `pg_stat_activity` view
* The `backend_type` field exists into the `pg_active_session_history` view and is always NULL (as a consequence of the previous remark)
* Some fields may be NULL depending on the version (for example, leader_pid is NULL for version <= 13.0...)

See how to query the view in this short video
-------------
Expand All @@ -174,15 +179,11 @@ See how to query the view in this short video
Contribution
------------

Please, notice, that `pgsentinel` is still in beta testing so may contain some bugs. Don't hesitate to raise
[issues at github](https://github.com/pgsentinel/pgsentinel/issues) with
your bug report.

If you're lacking of some functionality in `pgsentinel`
then you're welcome to make pull requests.

Author
-------

* Bertrand Drouvot <bdrouvot@gmail.com>,
Metz, France, [Twitter](https://twitter.com/BertrandDrouvot)
France, [Twitter](https://twitter.com/BertrandDrouvot)
11 changes: 2 additions & 9 deletions src/Makefile
@@ -1,19 +1,12 @@
MODULE_big = pgsentinel
OBJS = pgsentinel.o $(WIN32RES)
OBJS = pgsentinel.o get_parsedinfo.o

EXTENSION = pgsentinel
DATA = pgsentinel--1.0b.sql
DATA = pgsentinel--1.0.sql
PGFILEDESC = "pgsentinel - active session history"

LDFLAGS_SL += $(filter -lm, $(LIBS))

REGRESS_OPTS = --temp-config ./pgsentinel.conf
REGRESS = pgsentinel

# Disabled because these tests require "shared_preload_libraries=pgsentinel",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
220 changes: 220 additions & 0 deletions src/get_parsedinfo.c
@@ -0,0 +1,220 @@
/*
* get_parsedinfo.c
*
* Copyright (c) 2018, PgSentinel
*
* IDENTIFICATION:
* https://github.com/pgsentinel/pgsentinel
*
* LICENSE: GNU Affero General Public License v3.0
*/

#include "postgres.h"
#include "pgsentinel.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "miscadmin.h"
#include "access/twophase.h"
#include "parser/scansup.h"
#include "parser/analyze.h"
#include "access/hash.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "commands/extension.h"
#include "pgstat.h"

Datum get_parsedinfo(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(get_parsedinfo);

/* to create queryid in case of utility statements*/
#if PG_VERSION_NUM >= 110000
static uint64 getparsedinfo_hash64_string(const char *str, int len);
#else
static uint32 getparsedinfo_hash32_string(const char *str, int len);
#endif

/* Estimate amount of shared memory needed for proc entry*/
Size
proc_entry_memsize(void)
{
Size size;

/* ProcEntryArray */
size = mul_size(sizeof(procEntry), get_max_procs_count());
/* ProEntryQueryBuffer */
size = add_size(size, mul_size(pgstat_track_activity_query_size,
get_max_procs_count()));
/* ProEntryCmdTypeBuffer */
size = add_size(size, mul_size(NAMEDATALEN, get_max_procs_count()));
return size;
}

/* to create queryid in case of utility statements*/
#if PG_VERSION_NUM >= 110000
static uint64
getparsedinfo_hash64_string(const char *str, int len)
{
return DatumGetUInt64(hash_any_extended((const unsigned char *) str,
len, 0));
}
#else
static uint32
getparsedinfo_hash32_string(const char *str, int len)
{
return hash_any((const unsigned char *) str, len);
}
#endif

int
get_max_procs_count(void)
{
int count = 0;

count += MaxBackends;
count += NUM_AUXILIARY_PROCS;
count += max_prepared_xacts;

return count;
}

void
getparsedinfo_post_parse_analyze(ParseState *pstate, Query *query)
{

if (prev_post_parse_analyze_hook)
prev_post_parse_analyze_hook(pstate, query);

if (MyProc)
{
int i = MyProc - ProcGlobal->allProcs;
const char *querytext = pstate->p_sourcetext;
int minlen;
int query_len;
#if PG_VERSION_NUM >= 100000
int query_location = query->stmt_location;
query_len = query->stmt_len;

if (query_location >= 0)
{
Assert(query_location <= strlen(querytext));
querytext += query_location;
/* Length of 0 (or -1) means "rest of string" */
if (query_len <= 0)
query_len = strlen(querytext);
else
Assert(query_len <= strlen(querytext));
}
else
{
/* If query location is unknown, distrust query_len as well */
query_location = 0;
query_len = strlen(querytext);
}

/*
* Discard leading and trailing whitespace, too. Use scanner_isspace()
* not libc's isspace(), because we want to match the lexer's behavior.
*/
while (query_len > 0 && scanner_isspace(querytext[0]))
querytext++, query_location++, query_len--;
while (query_len > 0 && scanner_isspace(querytext[query_len - 1]))
query_len--;
#else
query_len = strlen(querytext);
#endif

minlen = Min(query_len,pgstat_track_activity_query_size-1);
memcpy(ProcEntryArray[i].query,querytext,minlen);
ProcEntryArray[i].query[minlen]='\0';
switch (query->commandType)
{
case CMD_SELECT:
ProcEntryArray[i].cmdtype="SELECT";
break;
case CMD_INSERT:
ProcEntryArray[i].cmdtype="INSERT";
break;
case CMD_UPDATE:
ProcEntryArray[i].cmdtype="UPDATE";
break;
case CMD_DELETE:
ProcEntryArray[i].cmdtype="DELETE";
break;
case CMD_UTILITY:
ProcEntryArray[i].cmdtype="UTILITY";
break;
case CMD_UNKNOWN:
ProcEntryArray[i].cmdtype="UNKNOWN";
break;
case CMD_NOTHING:
ProcEntryArray[i].cmdtype="NOTHING";
break;
}
/*
* For utility statements, we just hash the query string to get an ID.
*/
#if PG_VERSION_NUM >= 110000
if (query->queryId == UINT64CONST(0)) {
ProcEntryArray[i].queryid = getparsedinfo_hash64_string(querytext,
query_len);
#else
if (query->queryId == 0) {
ProcEntryArray[i].queryid = getparsedinfo_hash32_string(querytext,
query_len);
#endif
} else {
ProcEntryArray[i].queryid = query->queryId;
}
}
}

Datum
get_parsedinfo(PG_FUNCTION_ARGS)
{
int i;
/*procEntry *result;*/
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
MemoryContext per_query_ctx;
MemoryContext oldcontext;
Datum values[4];
bool nulls[4] = {0};

per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
oldcontext = MemoryContextSwitchTo(per_query_ctx);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
tupstore = tuplestore_begin_heap(true, false, work_mem);
rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext);

for (i = 0; i < ProcGlobal->allProcCount; i++)
{
PGPROC *proc = &ProcGlobal->allProcs[i];
if (proc != NULL && proc->pid != 0 && (proc->pid == PG_GETARG_INT32(0)
|| PG_GETARG_INT32(0) == -1))
{
values[0] = proc->pid;
if (Int64GetDatum(ProcEntryArray[i].queryid))
values[1] = Int64GetDatum(ProcEntryArray[i].queryid);
else
nulls[1] = true;
if (CStringGetTextDatum(ProcEntryArray[i].query))
values[2] = CStringGetTextDatum(ProcEntryArray[i].query);
else
nulls[2] = true;
if (CStringGetTextDatum(ProcEntryArray[i].cmdtype))
values[3] = CStringGetTextDatum(ProcEntryArray[i].cmdtype);
else
nulls[3] = true;

tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
}
tuplestore_donestoring(tupstore);
return (Datum) 0;
}
15 changes: 13 additions & 2 deletions src/pgsentinel--1.0b.sql → src/pgsentinel--1.0.sql
Expand Up @@ -6,6 +6,7 @@ CREATE FUNCTION pg_active_session_history(
OUT datid Oid,
OUT datname text,
OUT pid integer,
OUT leader_pid integer,
OUT usesysid Oid,
OUT usename text,
OUT application_name text,
Expand Down Expand Up @@ -46,7 +47,7 @@ CREATE FUNCTION pg_stat_statements_history(
OUT dbid Oid,
OUT queryid bigint,
OUT calls bigint,
OUT total_time double precision,
OUT total_exec_time double precision,
OUT rows bigint,
OUT shared_blks_hit bigint,
OUT shared_blks_read bigint,
Expand All @@ -59,7 +60,12 @@ CREATE FUNCTION pg_stat_statements_history(
OUT temp_blks_read bigint,
OUT temp_blks_written bigint,
OUT blk_read_time double precision,
OUT blk_write_time double precision
OUT blk_write_time double precision,
OUT plans bigint,
OUT total_plan_time double precision,
OUT wal_records bigint,
OUT wal_fpi bigint,
OUT wal_bytes numeric
)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pg_stat_statements_history'
Expand All @@ -70,3 +76,8 @@ CREATE VIEW pg_stat_statements_history AS
SELECT * FROM pg_stat_statements_history();

GRANT SELECT ON pg_stat_statements_history TO PUBLIC;

CREATE FUNCTION get_parsedinfo(IN int, OUT pid integer, OUT queryid bigint, OUT query text,OUT cmdtype text)
RETURNS SETOF RECORD
AS 'MODULE_PATHNAME','get_parsedinfo'
LANGUAGE C STRICT VOLATILE;

0 comments on commit b124855

Please sign in to comment.