Line data Source code
1 : /* -------------------------------------------------------------------------
2 : *
3 : * pgstat_function.c
4 : * Implementation of function statistics.
5 : *
6 : * This file contains the implementation of function statistics. It is kept
7 : * separate from pgstat.c to enforce the line between the statistics access /
8 : * storage implementation and the details about individual types of
9 : * statistics.
10 : *
11 : * Copyright (c) 2001-2026, PostgreSQL Global Development Group
12 : *
13 : * IDENTIFICATION
14 : * src/backend/utils/activity/pgstat_function.c
15 : * -------------------------------------------------------------------------
16 : */
17 :
18 : #include "postgres.h"
19 :
20 : #include "fmgr.h"
21 : #include "utils/inval.h"
22 : #include "utils/pgstat_internal.h"
23 : #include "utils/syscache.h"
24 :
25 :
26 : /* ----------
27 : * GUC parameters
28 : * ----------
29 : */
30 : int pgstat_track_functions = TRACK_FUNC_OFF;
31 :
32 :
33 : /*
34 : * Total time charged to functions so far in the current backend.
35 : * We use this to help separate "self" and "other" time charges.
36 : * (We assume this initializes to zero.)
37 : */
38 : static instr_time total_func_time;
39 :
40 :
41 : /*
42 : * Ensure that stats are dropped if transaction aborts.
43 : */
44 : void
45 8936 : pgstat_create_function(Oid proid)
46 : {
47 8936 : pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
48 : MyDatabaseId,
49 : proid);
50 8936 : }
51 :
52 : /*
53 : * Ensure that stats are dropped if transaction commits.
54 : *
55 : * NB: This is only reliable because pgstat_init_function_usage() does some
56 : * extra work. If other places start emitting function stats they likely need
57 : * similar logic.
58 : */
59 : void
60 4213 : pgstat_drop_function(Oid proid)
61 : {
62 4213 : pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
63 : MyDatabaseId,
64 : proid);
65 4213 : }
66 :
67 : /*
68 : * Initialize function call usage data.
69 : * Called by the executor before invoking a function.
70 : */
71 : void
72 11382138 : pgstat_init_function_usage(FunctionCallInfo fcinfo,
73 : PgStat_FunctionCallUsage *fcu)
74 : {
75 : PgStat_EntryRef *entry_ref;
76 : PgStat_FunctionCounts *pending;
77 : bool created_entry;
78 :
79 11382138 : if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
80 : {
81 : /* stats not wanted */
82 11382031 : fcu->fs = NULL;
83 11382031 : return;
84 : }
85 :
86 107 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION,
87 : MyDatabaseId,
88 107 : fcinfo->flinfo->fn_oid,
89 : &created_entry);
90 :
91 : /*
92 : * If no shared entry already exists, check if the function has been
93 : * deleted concurrently. This can go unnoticed until here because
94 : * executing a statement that just calls a function, does not trigger
95 : * cache invalidation processing. The reason we care about this case is
96 : * that otherwise we could create a new stats entry for an already dropped
97 : * function (for relations etc this is not possible because emitting stats
98 : * requires a lock for the relation to already have been acquired).
99 : *
100 : * It's somewhat ugly to have a behavioral difference based on
101 : * track_functions being enabled/disabled. But it seems acceptable, given
102 : * that there's already behavioral differences depending on whether the
103 : * function is the caches etc.
104 : *
105 : * For correctness it'd be sufficient to set ->dropped to true. However,
106 : * the accepted invalidation will commonly cause "low level" failures in
107 : * PL code, with an OID in the error message. Making this harder to
108 : * test...
109 : */
110 107 : if (created_entry)
111 : {
112 48 : AcceptInvalidationMessages();
113 48 : if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)))
114 : {
115 0 : pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId,
116 0 : fcinfo->flinfo->fn_oid);
117 0 : ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION),
118 : errmsg("function call to dropped function"));
119 : }
120 : }
121 :
122 107 : pending = entry_ref->pending;
123 :
124 107 : fcu->fs = pending;
125 :
126 : /* save stats for this function, later used to compensate for recursion */
127 107 : fcu->save_f_total_time = pending->total_time;
128 :
129 : /* save current backend-wide total time */
130 107 : fcu->save_total = total_func_time;
131 :
132 : /* get clock time as of function start */
133 107 : INSTR_TIME_SET_CURRENT(fcu->start);
134 : }
135 :
136 : /*
137 : * Calculate function call usage and update stat counters.
138 : * Called by the executor after invoking a function.
139 : *
140 : * In the case of a set-returning function that runs in value-per-call mode,
141 : * we will see multiple pgstat_init_function_usage/pgstat_end_function_usage
142 : * calls for what the user considers a single call of the function. The
143 : * finalize flag should be TRUE on the last call.
144 : */
145 : void
146 11378059 : pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
147 : {
148 11378059 : PgStat_FunctionCounts *fs = fcu->fs;
149 : instr_time total;
150 : instr_time others;
151 : instr_time self;
152 :
153 : /* stats not wanted? */
154 11378059 : if (fs == NULL)
155 11377952 : return;
156 :
157 : /* total elapsed time in this function call */
158 107 : INSTR_TIME_SET_CURRENT(total);
159 107 : INSTR_TIME_SUBTRACT(total, fcu->start);
160 :
161 : /* self usage: elapsed minus anything already charged to other calls */
162 107 : others = total_func_time;
163 107 : INSTR_TIME_SUBTRACT(others, fcu->save_total);
164 107 : self = total;
165 107 : INSTR_TIME_SUBTRACT(self, others);
166 :
167 : /* update backend-wide total time */
168 107 : INSTR_TIME_ADD(total_func_time, self);
169 :
170 : /*
171 : * Compute the new total_time as the total elapsed time added to the
172 : * pre-call value of total_time. This is necessary to avoid
173 : * double-counting any time taken by recursive calls of myself. (We do
174 : * not need any similar kluge for self time, since that already excludes
175 : * any recursive calls.)
176 : */
177 107 : INSTR_TIME_ADD(total, fcu->save_f_total_time);
178 :
179 : /* update counters in function stats table */
180 107 : if (finalize)
181 107 : fs->numcalls++;
182 107 : fs->total_time = total;
183 107 : INSTR_TIME_ADD(fs->self_time, self);
184 : }
185 :
186 : /*
187 : * Flush out pending stats for the entry
188 : *
189 : * If nowait is true and the lock could not be immediately acquired, returns
190 : * false without flushing the entry. Otherwise returns true.
191 : */
192 : bool
193 69 : pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
194 : {
195 : PgStat_FunctionCounts *localent;
196 : PgStatShared_Function *shfuncent;
197 :
198 69 : localent = (PgStat_FunctionCounts *) entry_ref->pending;
199 69 : shfuncent = (PgStatShared_Function *) entry_ref->shared_stats;
200 :
201 : /* localent always has non-zero content */
202 :
203 69 : if (!pgstat_lock_entry(entry_ref, nowait))
204 0 : return false;
205 :
206 69 : shfuncent->stats.numcalls += localent->numcalls;
207 69 : shfuncent->stats.total_time +=
208 69 : INSTR_TIME_GET_MICROSEC(localent->total_time);
209 69 : shfuncent->stats.self_time +=
210 69 : INSTR_TIME_GET_MICROSEC(localent->self_time);
211 :
212 69 : pgstat_unlock_entry(entry_ref);
213 :
214 69 : return true;
215 : }
216 :
217 : void
218 9 : pgstat_function_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
219 : {
220 9 : ((PgStatShared_Function *) header)->stats.stat_reset_timestamp = ts;
221 9 : }
222 :
223 : /*
224 : * find any existing PgStat_FunctionCounts entry for specified function
225 : *
226 : * If no entry, return NULL, don't create a new one
227 : */
228 : PgStat_FunctionCounts *
229 12 : find_funcstat_entry(Oid func_id)
230 : {
231 : PgStat_EntryRef *entry_ref;
232 :
233 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
234 :
235 12 : if (entry_ref)
236 9 : return entry_ref->pending;
237 3 : return NULL;
238 : }
239 :
240 : /*
241 : * Support function for the SQL-callable pgstat* functions. Returns
242 : * the collected statistics for one function or NULL.
243 : */
244 : PgStat_StatFuncEntry *
245 295 : pgstat_fetch_stat_funcentry(Oid func_id)
246 : {
247 295 : return (PgStat_StatFuncEntry *)
248 295 : pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
249 : }
|