Age Owner Branch data 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-2024, 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
739 andres@anarazel.de 45 :CBC 7283 : pgstat_create_function(Oid proid)
46 : : {
47 : 7283 : pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
48 : : MyDatabaseId,
49 : : proid);
50 : 7283 : }
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 : 3221 : pgstat_drop_function(Oid proid)
61 : : {
62 : 3221 : pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
63 : : MyDatabaseId,
64 : : proid);
65 : 3221 : }
66 : :
67 : : /*
68 : : * Initialize function call usage data.
69 : : * Called by the executor before invoking a function.
70 : : */
71 : : void
755 72 : 9231013 : 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 [ + + ]: 9231013 : if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
80 : : {
81 : : /* stats not wanted */
82 : 9230906 : fcu->fs = NULL;
83 : 9230906 : return;
84 : : }
85 : :
739 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 : : {
739 andres@anarazel.de 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 : :
739 andres@anarazel.de 122 :CBC 107 : pending = entry_ref->pending;
123 : :
395 michael@paquier.xyz 124 : 107 : fcu->fs = pending;
125 : :
126 : : /* save stats for this function, later used to compensate for recursion */
387 127 : 107 : fcu->save_f_total_time = pending->total_time;
128 : :
129 : : /* save current backend-wide total time */
755 andres@anarazel.de 130 : 107 : fcu->save_total = total_func_time;
131 : :
132 : : /* get clock time as of function start */
387 michael@paquier.xyz 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
755 andres@anarazel.de 146 : 9227016 : pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
147 : : {
148 : 9227016 : PgStat_FunctionCounts *fs = fcu->fs;
149 : : instr_time total;
150 : : instr_time others;
151 : : instr_time self;
152 : :
153 : : /* stats not wanted? */
154 [ + + ]: 9227016 : if (fs == NULL)
155 : 9226909 : return;
156 : :
157 : : /* total elapsed time in this function call */
387 michael@paquier.xyz 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 */
755 andres@anarazel.de 180 [ + - ]: 107 : if (finalize)
387 michael@paquier.xyz 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, this function returns false if lock could not
190 : : * immediately acquired, otherwise true is returned.
191 : : */
192 : : bool
739 andres@anarazel.de 193 : 69 : pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
194 : : {
195 : : PgStat_FunctionCounts *localent;
196 : : PgStatShared_Function *shfuncent;
197 : :
395 michael@paquier.xyz 198 : 69 : localent = (PgStat_FunctionCounts *) entry_ref->pending;
739 andres@anarazel.de 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))
739 andres@anarazel.de 204 :UBC 0 : return false;
205 : :
387 michael@paquier.xyz 206 :CBC 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 : :
739 andres@anarazel.de 212 : 69 : pgstat_unlock_entry(entry_ref);
213 : :
214 : 69 : 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 *
755 223 : 12 : find_funcstat_entry(Oid func_id)
224 : : {
225 : : PgStat_EntryRef *entry_ref;
226 : :
739 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 : 293 : pgstat_fetch_stat_funcentry(Oid func_id)
240 : : {
241 : 293 : return (PgStat_StatFuncEntry *)
242 : 293 : pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
243 : : }
|