Age Owner TLA 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-2023, 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
368 andres 45 CBC 11168 : pgstat_create_function(Oid proid)
46 : {
47 11168 : pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
48 : MyDatabaseId,
49 : proid);
50 11168 : }
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 3066 : pgstat_drop_function(Oid proid)
61 : {
62 3066 : pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
63 : MyDatabaseId,
64 : proid);
65 3066 : }
66 :
67 : /*
68 : * Initialize function call usage data.
69 : * Called by the executor before invoking a function.
70 : */
71 : void
384 72 8360619 : 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 8360619 : if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
80 : {
81 : /* stats not wanted */
82 8360515 : fcu->fs = NULL;
83 8360515 : return;
84 : }
85 :
368 86 104 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION,
87 : MyDatabaseId,
88 104 : 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 104 : if (created_entry)
111 : {
112 45 : AcceptInvalidationMessages();
113 45 : if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)))
114 : {
368 andres 115 UBC 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 :
368 andres 122 CBC 104 : pending = entry_ref->pending;
123 :
24 michael 124 GNC 104 : fcu->fs = pending;
125 :
126 : /* save stats for this function, later used to compensate for recursion */
16 127 104 : fcu->save_f_total_time = pending->total_time;
128 :
129 : /* save current backend-wide total time */
384 andres 130 CBC 104 : fcu->save_total = total_func_time;
131 :
132 : /* get clock time as of function start */
16 michael 133 GNC 104 : 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
384 andres 146 CBC 8357211 : pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
147 : {
148 8357211 : PgStat_FunctionCounts *fs = fcu->fs;
149 : instr_time total;
150 : instr_time others;
151 : instr_time self;
152 :
153 : /* stats not wanted? */
154 8357211 : if (fs == NULL)
155 8357107 : return;
156 :
157 : /* total elapsed time in this function call */
16 michael 158 GNC 104 : INSTR_TIME_SET_CURRENT(total);
159 104 : INSTR_TIME_SUBTRACT(total, fcu->start);
160 :
161 : /* self usage: elapsed minus anything already charged to other calls */
162 104 : others = total_func_time;
163 104 : INSTR_TIME_SUBTRACT(others, fcu->save_total);
164 104 : self = total;
165 104 : INSTR_TIME_SUBTRACT(self, others);
166 :
167 : /* update backend-wide total time */
168 104 : 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 104 : INSTR_TIME_ADD(total, fcu->save_f_total_time);
178 :
179 : /* update counters in function stats table */
384 andres 180 CBC 104 : if (finalize)
16 michael 181 GNC 104 : fs->numcalls++;
182 104 : fs->total_time = total;
183 104 : INSTR_TIME_ADD(fs->self_time, self);
184 : }
185 :
186 : /*
187 : * Flush out pending stats for the entry
188 : *
189 : * If nowait is true, this function returns false if lock could not
190 : * immediately acquired, otherwise true is returned.
191 : */
192 : bool
368 andres 193 CBC 66 : pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
194 : {
195 : PgStat_FunctionCounts *localent;
196 : PgStatShared_Function *shfuncent;
197 :
24 michael 198 GNC 66 : localent = (PgStat_FunctionCounts *) entry_ref->pending;
368 andres 199 CBC 66 : shfuncent = (PgStatShared_Function *) entry_ref->shared_stats;
200 :
201 : /* localent always has non-zero content */
202 :
203 66 : if (!pgstat_lock_entry(entry_ref, nowait))
368 andres 204 UBC 0 : return false;
205 :
16 michael 206 GNC 66 : shfuncent->stats.numcalls += localent->numcalls;
207 66 : shfuncent->stats.total_time +=
208 66 : INSTR_TIME_GET_MICROSEC(localent->total_time);
209 66 : shfuncent->stats.self_time +=
210 66 : INSTR_TIME_GET_MICROSEC(localent->self_time);
211 :
368 andres 212 CBC 66 : pgstat_unlock_entry(entry_ref);
213 :
214 66 : return true;
215 : }
216 :
217 : /*
218 : * find any existing PgStat_FunctionCounts entry for specified function
219 : *
220 : * If no entry, return NULL, don't create a new one
221 : */
222 : PgStat_FunctionCounts *
384 223 12 : find_funcstat_entry(Oid func_id)
224 : {
225 : PgStat_EntryRef *entry_ref;
226 :
368 227 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
228 :
229 12 : if (entry_ref)
230 9 : return entry_ref->pending;
231 3 : return NULL;
232 : }
233 :
234 : /*
235 : * Support function for the SQL-callable pgstat* functions. Returns
236 : * the collected statistics for one function or NULL.
237 : */
238 : PgStat_StatFuncEntry *
239 284 : pgstat_fetch_stat_funcentry(Oid func_id)
240 : {
241 284 : return (PgStat_StatFuncEntry *)
242 284 : pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
243 : }
|