summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/activity/pgstat_function.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:46:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:46:48 +0000
commit311bcfc6b3acdd6fd152798c7f287ddf74fa2a98 (patch)
tree0ec307299b1dada3701e42f4ca6eda57d708261e /src/backend/utils/activity/pgstat_function.c
parentInitial commit. (diff)
downloadpostgresql-15-upstream.tar.xz
postgresql-15-upstream.zip
Adding upstream version 15.4.upstream/15.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/utils/activity/pgstat_function.c')
-rw-r--r--src/backend/utils/activity/pgstat_function.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/backend/utils/activity/pgstat_function.c b/src/backend/utils/activity/pgstat_function.c
new file mode 100644
index 0000000..427d8c4
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_function.c
@@ -0,0 +1,243 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 "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_BackendFunctionEntry *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->f_counts;
+
+ /* save stats for this function, later used to compensate for recursion */
+ fcu->save_f_total_time = pending->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);
+}
+
+/*
+ * 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_BackendFunctionEntry *localent;
+ PgStatShared_Function *shfuncent;
+
+ localent = (PgStat_BackendFunctionEntry *) 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_counts.f_numcalls;
+ shfuncent->stats.f_total_time +=
+ INSTR_TIME_GET_MICROSEC(localent->f_counts.f_total_time);
+ shfuncent->stats.f_self_time +=
+ INSTR_TIME_GET_MICROSEC(localent->f_counts.f_self_time);
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/*
+ * 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_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);
+}