2022-03-21 15:02:25 -04:00
|
|
|
/* -------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* pgstat_function.c
|
|
|
|
|
* Implementation of function statistics.
|
|
|
|
|
*
|
|
|
|
|
* This file contains the implementation of function statistics. It is kept
|
|
|
|
|
* separate from pgstat.c to enforce the line between the statistics access /
|
|
|
|
|
* storage implementation and the details about individual types of
|
|
|
|
|
* statistics.
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
|
|
|
|
|
*
|
|
|
|
|
* IDENTIFICATION
|
|
|
|
|
* src/backend/utils/activity/pgstat_function.c
|
|
|
|
|
* -------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
|
|
#include "utils/pgstat_internal.h"
|
|
|
|
|
#include "utils/timestamp.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
|
* GUC parameters
|
|
|
|
|
* ----------
|
|
|
|
|
*/
|
|
|
|
|
int pgstat_track_functions = TRACK_FUNC_OFF;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Indicates if backend has some function stats that it hasn't yet
|
|
|
|
|
* sent to the collector.
|
|
|
|
|
*/
|
|
|
|
|
bool have_function_stats = false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Backends store per-function info that's waiting to be sent to the collector
|
|
|
|
|
* in this hash table (indexed by function OID).
|
|
|
|
|
*/
|
|
|
|
|
static HTAB *pgStatFunctions = NULL;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Total time charged to functions so far in the current backend.
|
|
|
|
|
* We use this to help separate "self" and "other" time charges.
|
|
|
|
|
* (We assume this initializes to zero.)
|
|
|
|
|
*/
|
|
|
|
|
static instr_time total_func_time;
|
|
|
|
|
|
|
|
|
|
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-06 21:22:22 -04:00
|
|
|
/*
|
|
|
|
|
* Ensure that stats are dropped if transaction aborts.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
pgstat_create_function(Oid proid)
|
|
|
|
|
{
|
|
|
|
|
pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
|
|
|
|
|
MyDatabaseId,
|
|
|
|
|
proid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Ensure that stats are dropped if transaction commits.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
pgstat_drop_function(Oid proid)
|
|
|
|
|
{
|
|
|
|
|
pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
|
|
|
|
|
MyDatabaseId,
|
|
|
|
|
proid);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 15:02:25 -04:00
|
|
|
/*
|
|
|
|
|
* Initialize function call usage data.
|
|
|
|
|
* Called by the executor before invoking a function.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
pgstat_init_function_usage(FunctionCallInfo fcinfo,
|
|
|
|
|
PgStat_FunctionCallUsage *fcu)
|
|
|
|
|
{
|
|
|
|
|
PgStat_BackendFunctionEntry *htabent;
|
|
|
|
|
bool found;
|
|
|
|
|
|
|
|
|
|
if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
|
|
|
|
|
{
|
|
|
|
|
/* stats not wanted */
|
|
|
|
|
fcu->fs = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!pgStatFunctions)
|
|
|
|
|
{
|
|
|
|
|
/* First time through - initialize function stat table */
|
|
|
|
|
HASHCTL hash_ctl;
|
|
|
|
|
|
|
|
|
|
hash_ctl.keysize = sizeof(Oid);
|
|
|
|
|
hash_ctl.entrysize = sizeof(PgStat_BackendFunctionEntry);
|
|
|
|
|
pgStatFunctions = hash_create("Function stat entries",
|
|
|
|
|
PGSTAT_FUNCTION_HASH_SIZE,
|
|
|
|
|
&hash_ctl,
|
|
|
|
|
HASH_ELEM | HASH_BLOBS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the stats entry for this function, create if necessary */
|
|
|
|
|
htabent = hash_search(pgStatFunctions, &fcinfo->flinfo->fn_oid,
|
|
|
|
|
HASH_ENTER, &found);
|
|
|
|
|
if (!found)
|
|
|
|
|
MemSet(&htabent->f_counts, 0, sizeof(PgStat_FunctionCounts));
|
|
|
|
|
|
|
|
|
|
fcu->fs = &htabent->f_counts;
|
|
|
|
|
|
|
|
|
|
/* save stats for this function, later used to compensate for recursion */
|
|
|
|
|
fcu->save_f_total_time = htabent->f_counts.f_total_time;
|
|
|
|
|
|
|
|
|
|
/* save current backend-wide total time */
|
|
|
|
|
fcu->save_total = total_func_time;
|
|
|
|
|
|
|
|
|
|
/* get clock time as of function start */
|
|
|
|
|
INSTR_TIME_SET_CURRENT(fcu->f_start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Calculate function call usage and update stat counters.
|
|
|
|
|
* Called by the executor after invoking a function.
|
|
|
|
|
*
|
|
|
|
|
* In the case of a set-returning function that runs in value-per-call mode,
|
|
|
|
|
* we will see multiple pgstat_init_function_usage/pgstat_end_function_usage
|
|
|
|
|
* calls for what the user considers a single call of the function. The
|
|
|
|
|
* finalize flag should be TRUE on the last call.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
|
|
|
|
|
{
|
|
|
|
|
PgStat_FunctionCounts *fs = fcu->fs;
|
|
|
|
|
instr_time f_total;
|
|
|
|
|
instr_time f_others;
|
|
|
|
|
instr_time f_self;
|
|
|
|
|
|
|
|
|
|
/* stats not wanted? */
|
|
|
|
|
if (fs == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* total elapsed time in this function call */
|
|
|
|
|
INSTR_TIME_SET_CURRENT(f_total);
|
|
|
|
|
INSTR_TIME_SUBTRACT(f_total, fcu->f_start);
|
|
|
|
|
|
|
|
|
|
/* self usage: elapsed minus anything already charged to other calls */
|
|
|
|
|
f_others = total_func_time;
|
|
|
|
|
INSTR_TIME_SUBTRACT(f_others, fcu->save_total);
|
|
|
|
|
f_self = f_total;
|
|
|
|
|
INSTR_TIME_SUBTRACT(f_self, f_others);
|
|
|
|
|
|
|
|
|
|
/* update backend-wide total time */
|
|
|
|
|
INSTR_TIME_ADD(total_func_time, f_self);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Compute the new f_total_time as the total elapsed time added to the
|
|
|
|
|
* pre-call value of f_total_time. This is necessary to avoid
|
|
|
|
|
* double-counting any time taken by recursive calls of myself. (We do
|
|
|
|
|
* not need any similar kluge for self time, since that already excludes
|
|
|
|
|
* any recursive calls.)
|
|
|
|
|
*/
|
|
|
|
|
INSTR_TIME_ADD(f_total, fcu->save_f_total_time);
|
|
|
|
|
|
|
|
|
|
/* update counters in function stats table */
|
|
|
|
|
if (finalize)
|
|
|
|
|
fs->f_numcalls++;
|
|
|
|
|
fs->f_total_time = f_total;
|
|
|
|
|
INSTR_TIME_ADD(fs->f_self_time, f_self);
|
|
|
|
|
|
|
|
|
|
/* indicate that we have something to send */
|
|
|
|
|
have_function_stats = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Subroutine for pgstat_report_stat: populate and send a function stat message
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
pgstat_send_funcstats(void)
|
|
|
|
|
{
|
|
|
|
|
/* we assume this inits to all zeroes: */
|
|
|
|
|
static const PgStat_FunctionCounts all_zeroes;
|
|
|
|
|
|
|
|
|
|
PgStat_MsgFuncstat msg;
|
|
|
|
|
PgStat_BackendFunctionEntry *entry;
|
|
|
|
|
HASH_SEQ_STATUS fstat;
|
|
|
|
|
|
|
|
|
|
if (pgStatFunctions == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_FUNCSTAT);
|
|
|
|
|
msg.m_databaseid = MyDatabaseId;
|
|
|
|
|
msg.m_nentries = 0;
|
|
|
|
|
|
|
|
|
|
hash_seq_init(&fstat, pgStatFunctions);
|
|
|
|
|
while ((entry = (PgStat_BackendFunctionEntry *) hash_seq_search(&fstat)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
PgStat_FunctionEntry *m_ent;
|
|
|
|
|
|
|
|
|
|
/* Skip it if no counts accumulated since last time */
|
|
|
|
|
if (memcmp(&entry->f_counts, &all_zeroes,
|
|
|
|
|
sizeof(PgStat_FunctionCounts)) == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* need to convert format of time accumulators */
|
|
|
|
|
m_ent = &msg.m_entry[msg.m_nentries];
|
|
|
|
|
m_ent->f_id = entry->f_id;
|
|
|
|
|
m_ent->f_numcalls = entry->f_counts.f_numcalls;
|
|
|
|
|
m_ent->f_total_time = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_total_time);
|
|
|
|
|
m_ent->f_self_time = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_self_time);
|
|
|
|
|
|
|
|
|
|
if (++msg.m_nentries >= PGSTAT_NUM_FUNCENTRIES)
|
|
|
|
|
{
|
|
|
|
|
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
|
|
|
|
|
msg.m_nentries * sizeof(PgStat_FunctionEntry));
|
|
|
|
|
msg.m_nentries = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* reset the entry's counts */
|
|
|
|
|
MemSet(&entry->f_counts, 0, sizeof(PgStat_FunctionCounts));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msg.m_nentries > 0)
|
|
|
|
|
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
|
|
|
|
|
msg.m_nentries * sizeof(PgStat_FunctionEntry));
|
|
|
|
|
|
|
|
|
|
have_function_stats = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2022-04-04 15:14:34 -04:00
|
|
|
* find any existing PgStat_BackendFunctionEntry entry for specified function
|
2022-03-21 15:02:25 -04:00
|
|
|
*
|
|
|
|
|
* If no entry, return NULL, don't create a new one
|
|
|
|
|
*/
|
|
|
|
|
PgStat_BackendFunctionEntry *
|
|
|
|
|
find_funcstat_entry(Oid func_id)
|
|
|
|
|
{
|
|
|
|
|
pgstat_assert_is_up();
|
|
|
|
|
|
|
|
|
|
if (pgStatFunctions == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
return (PgStat_BackendFunctionEntry *) hash_search(pgStatFunctions,
|
|
|
|
|
(void *) &func_id,
|
|
|
|
|
HASH_FIND, NULL);
|
|
|
|
|
}
|