mirror of https://github.com/postgres/postgres
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
6.1 KiB
225 lines
6.1 KiB
/* -------------------------------------------------------------------------
|
|
*
|
|
* 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;
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* find_funcstat_entry - find any existing PgStat_BackendFunctionEntry entry
|
|
* for specified function
|
|
*
|
|
* 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);
|
|
}
|
|
|