| /* ------------------------------------------------------------------------- |
| * |
| * 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->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->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 total; |
| instr_time others; |
| instr_time self; |
| |
| /* stats not wanted? */ |
| if (fs == NULL) |
| return; |
| |
| /* total elapsed time in this function call */ |
| INSTR_TIME_SET_CURRENT(total); |
| INSTR_TIME_SUBTRACT(total, fcu->start); |
| |
| /* self usage: elapsed minus anything already charged to other calls */ |
| others = total_func_time; |
| INSTR_TIME_SUBTRACT(others, fcu->save_total); |
| self = total; |
| INSTR_TIME_SUBTRACT(self, others); |
| |
| /* update backend-wide total time */ |
| INSTR_TIME_ADD(total_func_time, self); |
| |
| /* |
| * Compute the new total_time as the total elapsed time added to the |
| * pre-call value of 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(total, fcu->save_f_total_time); |
| |
| /* update counters in function stats table */ |
| if (finalize) |
| fs->numcalls++; |
| fs->total_time = total; |
| INSTR_TIME_ADD(fs->self_time, self); |
| } |
| |
| /* |
| * Flush out pending stats for the entry |
| * |
| * If nowait is true and the lock could not be immediately acquired, returns |
| * false without flushing the entry. Otherwise returns true. |
| */ |
| 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.numcalls += localent->numcalls; |
| shfuncent->stats.total_time += |
| INSTR_TIME_GET_MICROSEC(localent->total_time); |
| shfuncent->stats.self_time += |
| INSTR_TIME_GET_MICROSEC(localent->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); |
| } |