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 45 14620 : pgstat_create_function(Oid proid) 46 : { 47 14620 : pgstat_create_transactional(PGSTAT_KIND_FUNCTION, 48 : MyDatabaseId, 49 : proid); 50 14620 : } 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 6500 : pgstat_drop_function(Oid proid) 61 : { 62 6500 : pgstat_drop_transactional(PGSTAT_KIND_FUNCTION, 63 : MyDatabaseId, 64 : proid); 65 6500 : } 66 : 67 : /* 68 : * Initialize function call usage data. 69 : * Called by the executor before invoking a function. 70 : */ 71 : void 72 20129034 : 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 20129034 : if (pgstat_track_functions <= fcinfo->flinfo->fn_stats) 80 : { 81 : /* stats not wanted */ 82 20128820 : fcu->fs = NULL; 83 20128820 : return; 84 : } 85 : 86 214 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION, 87 : MyDatabaseId, 88 214 : 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 214 : if (created_entry) 111 : { 112 96 : AcceptInvalidationMessages(); 113 96 : 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 214 : pending = entry_ref->pending; 123 : 124 214 : fcu->fs = pending; 125 : 126 : /* save stats for this function, later used to compensate for recursion */ 127 214 : fcu->save_f_total_time = pending->total_time; 128 : 129 : /* save current backend-wide total time */ 130 214 : fcu->save_total = total_func_time; 131 : 132 : /* get clock time as of function start */ 133 214 : 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 20120978 : pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize) 147 : { 148 20120978 : PgStat_FunctionCounts *fs = fcu->fs; 149 : instr_time total; 150 : instr_time others; 151 : instr_time self; 152 : 153 : /* stats not wanted? */ 154 20120978 : if (fs == NULL) 155 20120764 : return; 156 : 157 : /* total elapsed time in this function call */ 158 214 : INSTR_TIME_SET_CURRENT(total); 159 214 : INSTR_TIME_SUBTRACT(total, fcu->start); 160 : 161 : /* self usage: elapsed minus anything already charged to other calls */ 162 214 : others = total_func_time; 163 214 : INSTR_TIME_SUBTRACT(others, fcu->save_total); 164 214 : self = total; 165 214 : INSTR_TIME_SUBTRACT(self, others); 166 : 167 : /* update backend-wide total time */ 168 214 : 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 214 : INSTR_TIME_ADD(total, fcu->save_f_total_time); 178 : 179 : /* update counters in function stats table */ 180 214 : if (finalize) 181 214 : fs->numcalls++; 182 214 : fs->total_time = total; 183 214 : 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 193 138 : pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) 194 : { 195 : PgStat_FunctionCounts *localent; 196 : PgStatShared_Function *shfuncent; 197 : 198 138 : localent = (PgStat_FunctionCounts *) entry_ref->pending; 199 138 : shfuncent = (PgStatShared_Function *) entry_ref->shared_stats; 200 : 201 : /* localent always has non-zero content */ 202 : 203 138 : if (!pgstat_lock_entry(entry_ref, nowait)) 204 0 : return false; 205 : 206 138 : shfuncent->stats.numcalls += localent->numcalls; 207 138 : shfuncent->stats.total_time += 208 138 : INSTR_TIME_GET_MICROSEC(localent->total_time); 209 138 : shfuncent->stats.self_time += 210 138 : INSTR_TIME_GET_MICROSEC(localent->self_time); 211 : 212 138 : pgstat_unlock_entry(entry_ref); 213 : 214 138 : 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 * 223 24 : find_funcstat_entry(Oid func_id) 224 : { 225 : PgStat_EntryRef *entry_ref; 226 : 227 24 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); 228 : 229 24 : if (entry_ref) 230 18 : return entry_ref->pending; 231 6 : 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 586 : pgstat_fetch_stat_funcentry(Oid func_id) 240 : { 241 586 : return (PgStat_StatFuncEntry *) 242 586 : pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); 243 : }