mirror of
https://github.com/postgres/postgres.git
synced 2025-05-12 16:21:30 +03:00
This structure included only PgStat_FunctionCounts, and removing it facilitates some upcoming refactoring for pgstatfuncs.c to use more macros rather that mostly-duplicated functions. Author: Bertrand Drouvot Reviewed-by: Nathan Bossart Discussion: https://postgr.es/m/11d531fe-52fc-c6ea-7e8e-62f1b6ec626e@gmail.com
244 lines
6.8 KiB
C
244 lines
6.8 KiB
C
/* -------------------------------------------------------------------------
|
|
*
|
|
* 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-2023, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/activity/pgstat_function.c
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "fmgr.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/pgstat_internal.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/* ----------
|
|
* GUC parameters
|
|
* ----------
|
|
*/
|
|
int pgstat_track_functions = TRACK_FUNC_OFF;
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* NB: This is only reliable because pgstat_init_function_usage() does some
|
|
* extra work. If other places start emitting function stats they likely need
|
|
* similar logic.
|
|
*/
|
|
void
|
|
pgstat_drop_function(Oid proid)
|
|
{
|
|
pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
|
|
MyDatabaseId,
|
|
proid);
|
|
}
|
|
|
|
/*
|
|
* Initialize function call usage data.
|
|
* Called by the executor before invoking a function.
|
|
*/
|
|
void
|
|
pgstat_init_function_usage(FunctionCallInfo fcinfo,
|
|
PgStat_FunctionCallUsage *fcu)
|
|
{
|
|
PgStat_EntryRef *entry_ref;
|
|
PgStat_FunctionCounts *pending;
|
|
bool created_entry;
|
|
|
|
if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
|
|
{
|
|
/* stats not wanted */
|
|
fcu->fs = NULL;
|
|
return;
|
|
}
|
|
|
|
entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION,
|
|
MyDatabaseId,
|
|
fcinfo->flinfo->fn_oid,
|
|
&created_entry);
|
|
|
|
/*
|
|
* If no shared entry already exists, check if the function has been
|
|
* deleted concurrently. This can go unnoticed until here because
|
|
* executing a statement that just calls a function, does not trigger
|
|
* cache invalidation processing. The reason we care about this case is
|
|
* that otherwise we could create a new stats entry for an already dropped
|
|
* function (for relations etc this is not possible because emitting stats
|
|
* requires a lock for the relation to already have been acquired).
|
|
*
|
|
* It's somewhat ugly to have a behavioral difference based on
|
|
* track_functions being enabled/disabled. But it seems acceptable, given
|
|
* that there's already behavioral differences depending on whether the
|
|
* function is the caches etc.
|
|
*
|
|
* For correctness it'd be sufficient to set ->dropped to true. However,
|
|
* the accepted invalidation will commonly cause "low level" failures in
|
|
* PL code, with an OID in the error message. Making this harder to
|
|
* test...
|
|
*/
|
|
if (created_entry)
|
|
{
|
|
AcceptInvalidationMessages();
|
|
if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)))
|
|
{
|
|
pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId,
|
|
fcinfo->flinfo->fn_oid);
|
|
ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION),
|
|
errmsg("function call to dropped function"));
|
|
}
|
|
}
|
|
|
|
pending = entry_ref->pending;
|
|
|
|
fcu->fs = pending;
|
|
|
|
/* save stats for this function, later used to compensate for recursion */
|
|
fcu->save_f_total_time = pending->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);
|
|
}
|
|
|
|
/*
|
|
* Flush out pending stats for the entry
|
|
*
|
|
* If nowait is true, this function returns false if lock could not
|
|
* immediately acquired, otherwise true is returned.
|
|
*/
|
|
bool
|
|
pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
|
|
{
|
|
PgStat_FunctionCounts *localent;
|
|
PgStatShared_Function *shfuncent;
|
|
|
|
localent = (PgStat_FunctionCounts *) entry_ref->pending;
|
|
shfuncent = (PgStatShared_Function *) entry_ref->shared_stats;
|
|
|
|
/* localent always has non-zero content */
|
|
|
|
if (!pgstat_lock_entry(entry_ref, nowait))
|
|
return false;
|
|
|
|
shfuncent->stats.f_numcalls += localent->f_numcalls;
|
|
shfuncent->stats.f_total_time +=
|
|
INSTR_TIME_GET_MICROSEC(localent->f_total_time);
|
|
shfuncent->stats.f_self_time +=
|
|
INSTR_TIME_GET_MICROSEC(localent->f_self_time);
|
|
|
|
pgstat_unlock_entry(entry_ref);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* find any existing PgStat_FunctionCounts entry for specified function
|
|
*
|
|
* If no entry, return NULL, don't create a new one
|
|
*/
|
|
PgStat_FunctionCounts *
|
|
find_funcstat_entry(Oid func_id)
|
|
{
|
|
PgStat_EntryRef *entry_ref;
|
|
|
|
entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
|
|
|
|
if (entry_ref)
|
|
return entry_ref->pending;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Support function for the SQL-callable pgstat* functions. Returns
|
|
* the collected statistics for one function or NULL.
|
|
*/
|
|
PgStat_StatFuncEntry *
|
|
pgstat_fetch_stat_funcentry(Oid func_id)
|
|
{
|
|
return (PgStat_StatFuncEntry *)
|
|
pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
|
|
}
|