On 01/17/2012 11:13 AM, Joel Jacobson wrote:
> Since you only care about the parentfuncid in one level, it looks like
> you will only be able to get a total call graph of all possible
> function calls, and not each unique call graph per transaction.
True, for my purposes (function dependencies and performance analysis)
the global graph is sufficient.
> Also, why remove the parent oid when uploading the statistics to the
> collector?
> It would be nice to have the statistics for each function per parent,
> to see where you have a bottleneck which might only be occurring in a
> function when called from a specific parent.
I guess I was just lazy at the time I wrote it. But it shouldn't be too
much of an effort to store the global call graph in statistics
collector. Unique call graphs would be somewhat more complicated I
guess.
>> There is a patch for this and we do use it in production for occasional
>> troubleshooting and dependency analysis. Can't attach immediately
>> though -- it has some extra cruft in it that needs to be cleaned up.
>
> I would highly appreciate a patch, don't worry about cleaning up, I
> can do that, unless it's some code you can't share for other reasons.
>
Patch attached. It was developed against 9.1, but also applies to
HEAD but gives some fuzz and offsets.
It adds 2 GUC variables: log_function_calls and log_usage_stats. The
first just output function statistics to log (with no parent info).
With log_usage_stats enabled, it also outputs detailed function usage
plus relation usage. So you basically get output such as:
# select * from pgq.get_consumer_info();
LOG: duration: 11.153 ms statement: select * from pgq.get_consumer_info();
LOG: function call: pgq.get_consumer_info(0) calls=1 time=9726
self_time=536
LOG: USAGE STATISTICS
DETAIL: ! object access stats:
! F 1892464226 0 pgq.get_consumer_info(0) calls=1 time=9726 self_time=536
! F 1892464228 1892464226 pgq.get_consumer_info(2) calls=1 time=9190
self_time=9190
! r 167558000 pgq.queue: blks_read=28 blks_hit=28
! r 167557988 pgq.consumer: blks_read=56 blks_hit=56
! r 167558021 pgq.subscription: blks_read=54 blks_hit=50
! r 167558050 pgq.tick: blks_read=103 blks_hit=102
! i 1892464189 pgq.queue_pkey: scans=1 tup_ret=37 tup_fetch=37
blks_read=2 blks_hit=2
! i 167557995 pgq.consumer_pkey: scans=56 tup_ret=56 tup_fetch=56
blks_read=57 blks_hit=56
! i 1892464195 pgq.subscription_pkey: scans=37 tup_ret=156 tup_fetch=56
blks_read=127 blks_hit=123
! i 167558058 pgq.tick_pkey: scans=112 tup_ret=103 tup_fetch=103
blks_read=367 blks_hit=366
> funcid->parentfuncid might be sufficient for performance
> optimizations, but to automatically generate directional graphs of all
> unique call graphs in run-time, you would need all the unique pairs of
> funcid->parentfuncid as a singel column, probably a sorted array of
> oids[][], example: [[1,2],[1,3],[2,4],[2,5]] if the call craph would
> be {1->2, 1->3, 2->4, 2->5}.
>
Hmm, array would probably work, although I wonder if there is a
more elegant solution ...
regards,
Martin
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 5ed6e83..2e66020 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -42,6 +42,7 @@
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "libpq/ip.h"
#include "libpq/libpq.h"
@@ -63,7 +64,8 @@
#include "utils/ps_status.h"
#include "utils/rel.h"
#include "utils/tqual.h"
-
+#include "utils/syscache.h"
+#include "utils/lsyscache.h"
/* ----------
* Paths for the statistics files (relative to installation's $PGDATA).
@@ -113,6 +115,12 @@ bool pgstat_track_counts = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
+/*
+ * Last function call parent. InvalidOid is used to indicate
+ * top-level functions.
+ */
+Oid current_function_parent = InvalidOid;
+
/* ----------
* Built from GUC parameter
* ----------
@@ -258,8 +266,11 @@ static void pgstat_read_current_status(void);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
+static void pgstat_report_functions(void);
+static void pgstat_report_usage(void);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
+static PgStat_TableStatus *new_tsa_entry(TabStatusArray *entry, Oid rel_id, bool isshared);
static void pgstat_setup_memcxt(void);
@@ -697,6 +708,12 @@ pgstat_report_stat(bool force)
last_report = now;
/*
+ * First report function calls and object usage.
+ */
+ pgstat_report_functions();
+ pgstat_report_usage();
+
+ /*
* Scan through the TabStatusArray struct(s) to find tables that actually
* have counts, and build messages to send. We have to separate shared
* relations from regular ones because the databaseid field in the message
@@ -804,9 +821,6 @@ pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg)
static void
pgstat_send_funcstats(void)
{
- /* we assume this inits to all zeroes: */
- static const PgStat_FunctionCounts all_zeroes;
-
PgStat_MsgFuncstat msg;
PgStat_BackendFunctionEntry *entry;
HASH_SEQ_STATUS fstat;
@@ -818,37 +832,31 @@ pgstat_send_funcstats(void)
msg.m_databaseid = MyDatabaseId;
msg.m_nentries = 0;
+ /*
+ * Pack the function stats into message structures and send
+ * them to the collector. Don't bother merging the stats for
+ * the same function with different parents -- just send the
+ * entries individually and let the collector sum it up.
+ */
hash_seq_init(&fstat, pgStatFunctions);
- while ((entry = (PgStat_BackendFunctionEntry *) hash_seq_search(&fstat)) != NULL)
+ while ((entry = hash_seq_search(&fstat)) != NULL)
{
- PgStat_FunctionEntry *m_ent;
+ msg.m_entry[msg.m_nentries].f_id = entry->f_key.f_id;
+ msg.m_entry[msg.m_nentries].f_counts = entry->counts;
- /* Skip it if no counts accumulated since last time */
- if (memcmp(&entry->f_counts, &all_zeroes,
- sizeof(PgStat_FunctionCounts)) == 0)
- continue;
-
- /* need to convert format of time accumulators */
- m_ent = &msg.m_entry[msg.m_nentries];
- m_ent->f_id = entry->f_id;
- m_ent->f_numcalls = entry->f_counts.f_numcalls;
- m_ent->f_time = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_time);
- m_ent->f_time_self = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_time_self);
-
- if (++msg.m_nentries >= PGSTAT_NUM_FUNCENTRIES)
- {
- pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
- msg.m_nentries * sizeof(PgStat_FunctionEntry));
- msg.m_nentries = 0;
- }
+ memset(&entry->counts, 0, sizeof(PgStat_FunctionCounts));
- /* reset the entry's counts */
- MemSet(&entry->f_counts, 0, sizeof(PgStat_FunctionCounts));
+ if (++msg.m_nentries >= PGSTAT_NUM_FUNCENTRIES)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_MsgFunctionEntry));
+ msg.m_nentries = 0;
+ }
}
if (msg.m_nentries > 0)
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
- msg.m_nentries * sizeof(PgStat_FunctionEntry));
+ msg.m_nentries * sizeof(PgStat_MsgFunctionEntry));
have_function_stats = false;
}
@@ -1079,6 +1087,163 @@ pgstat_collect_oids(Oid catalogid)
/* ----------
+ * pgstat_report_functions() -
+ *
+ * Report top-level function calls to system log.
+ * ----------
+ */
+static void
+pgstat_report_functions(void)
+{
+ PgStat_BackendFunctionEntry *entry;
+ HASH_SEQ_STATUS fstat;
+
+ if (!log_function_calls || IsAutoVacuumWorkerProcess())
+ return;
+
+ /*
+ * Go through the functions that have stats. Dump only the
+ * top-level calls.
+ */
+ if (pgStatFunctions != NULL)
+ {
+ hash_seq_init(&fstat, pgStatFunctions);
+ while ((entry = hash_seq_search(&fstat)) != NULL)
+ {
+ /* log top-level function calls if so requested */
+ if (entry->counts.f_numcalls > 0 && !OidIsValid(entry->f_key.f_parent))
+ elog(LOG, "function call: %s.%s(%d) calls=" UINT64_FORMAT
+ " time=" UINT64_FORMAT " self_time=" UINT64_FORMAT,
+ entry->f_schema,
+ entry->f_name,
+ entry->f_nargs,
+ entry->counts.f_numcalls,
+ INSTR_TIME_GET_MICROSEC(entry->counts.f_time),
+ INSTR_TIME_GET_MICROSEC(entry->counts.f_time_self));
+ }
+ }
+}
+
+/* ----------
+ * pgstat_report_usage() -
+ *
+ * Report relation and function usage for the client. The report is
+ * formatted similarly to log_executor_stats.
+ * ----------
+ */
+static void
+pgstat_report_usage(void)
+{
+ static const PgStat_TableCounts tc_all_zeroes;
+ PgStat_BackendFunctionEntry *entry;
+ HASH_SEQ_STATUS fstat;
+ TabStatusArray *tsa;
+ StringInfoData str;
+ bool log_usage = false;
+ int i;
+
+ if (!log_usage_stats || IsAutoVacuumWorkerProcess())
+ return;
+
+ initStringInfo(&str);
+ appendStringInfo(&str, "! object access stats:\n");
+
+ /*
+ * Go through the functions that have stats. We produce as many
+ * detail rows as necessary to list all the invocations by different
+ * parents of the function. This is verbose, but allows for more
+ * fine-grained performance analysis later.
+ */
+ if (pgStatFunctions != NULL)
+ {
+ hash_seq_init(&fstat, pgStatFunctions);
+ while ((entry = hash_seq_search(&fstat)) != NULL)
+ {
+ if (!entry->counts.f_numcalls > 0)
+ /* nothing to do */
+ continue;
+
+ appendStringInfo(&str, "! F %u %u %s.%s(%d) calls=" UINT64_FORMAT
+ " time=" UINT64_FORMAT
+ " self_time=" UINT64_FORMAT "\n",
+ entry->f_key.f_id,
+ entry->f_key.f_parent,
+ entry->f_schema,
+ entry->f_name,
+ entry->f_nargs,
+ entry->counts.f_numcalls,
+ INSTR_TIME_GET_MICROSEC(entry->counts.f_time),
+ INSTR_TIME_GET_MICROSEC(entry->counts.f_time_self));
+
+ log_usage = true;
+ }
+ }
+
+ /*
+ * Now the tables. Here we actually report any relations that have
+ * stats counters, use relkind to distinguish.
+ */
+ if (log_usage_stats && pgStatTabList->tsa_used > 0)
+ {
+ for (tsa = pgStatTabList; tsa != NULL; tsa = tsa->tsa_next)
+ {
+ for (i = 0; i < tsa->tsa_used; i++)
+ {
+ PgStat_TableStatus *entry = &tsa->tsa_entries[i];
+ PgStat_TableCounts *tc = &entry->t_counts;
+
+ if (entry->t_system)
+ /* Don't report system tables */
+ continue;
+ if (memcmp(tc, &tc_all_zeroes, sizeof(*tc)) == 0)
+ /* All zeroes, nothing to report */
+ continue;
+
+#define REPORT_TC_COUNTER(field,label) \
+ if (tc->field) \
+ appendStringInfo(&str, " %s=" UINT64_FORMAT, (label), tc->field)
+
+ appendStringInfo(&str, "! %c %u %s.%s:",
+ entry->t_relkind,
+ entry->t_id,
+ entry->t_schema,
+ entry->t_name);
+
+ REPORT_TC_COUNTER(t_numscans, "scans");
+ REPORT_TC_COUNTER(t_tuples_returned, "tup_ret");
+ REPORT_TC_COUNTER(t_tuples_fetched, "tup_fetch");
+ REPORT_TC_COUNTER(t_tuples_inserted, "n_tup_ins");
+ REPORT_TC_COUNTER(t_tuples_updated, "n_tup_upd");
+ REPORT_TC_COUNTER(t_tuples_deleted, "n_tup_del");
+ REPORT_TC_COUNTER(t_tuples_hot_updated, "n_tup_hot_upd");
+ REPORT_TC_COUNTER(t_delta_live_tuples, "n_delta_live_tuples");
+ REPORT_TC_COUNTER(t_delta_dead_tuples, "n_delta_dead_tuples");
+ REPORT_TC_COUNTER(t_changed_tuples, "n_changed_tuples");
+ REPORT_TC_COUNTER(t_blocks_fetched, "blks_read");
+ REPORT_TC_COUNTER(t_blocks_hit, "blks_hit");
+
+ appendStringInfo(&str, "\n");
+ log_usage = true;
+ }
+ }
+ }
+
+ /*
+ * Found something to report - spit it out.
+ */
+ if (log_usage)
+ {
+ str.data[--str.len] = '\0';
+ ereport(LOG,
+ (errmsg_internal("%s", "USAGE STATISTICS"),
+ errdetail("%s", str.data)));
+ }
+
+ pfree(str.data);
+}
+
+
+/* ----------
* pgstat_drop_database() -
*
* Tell the collector that we just dropped a database.
@@ -1380,8 +1545,10 @@ void
pgstat_init_function_usage(FunctionCallInfoData *fcinfo,
PgStat_FunctionCallUsage *fcu)
{
- PgStat_BackendFunctionEntry *htabent;
bool found;
+ Oid funcid;
+ MemoryContext oldcontext;
+ FunctionStatKey key;
if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
{
@@ -1390,15 +1557,19 @@ pgstat_init_function_usage(FunctionCallInfoData *fcinfo,
return;
}
+ memset(&fcu->counts, 0, sizeof(fcu->counts));
+ funcid = fcinfo->flinfo->fn_oid;
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
if (!pgStatFunctions)
{
/* First time through - initialize function stat table */
HASHCTL hash_ctl;
memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.keysize = sizeof(key);
hash_ctl.entrysize = sizeof(PgStat_BackendFunctionEntry);
- hash_ctl.hash = oid_hash;
+ hash_ctl.hash = tag_hash;
pgStatFunctions = hash_create("Function stat entries",
PGSTAT_FUNCTION_HASH_SIZE,
&hash_ctl,
@@ -1406,21 +1577,45 @@ pgstat_init_function_usage(FunctionCallInfoData *fcinfo,
}
/* Get the stats entry for this function, create if necessary */
- htabent = hash_search(pgStatFunctions, &fcinfo->flinfo->fn_oid,
- HASH_ENTER, &found);
+ key.f_id = funcid;
+ key.f_parent = current_function_parent;
+ fcu->fs = hash_search(pgStatFunctions, &key, HASH_ENTER, &found);
+
if (!found)
- MemSet(&htabent->f_counts, 0, sizeof(PgStat_FunctionCounts));
+ {
+ Form_pg_proc functup;
+ HeapTuple tp;
- fcu->fs = &htabent->f_counts;
+ tp = SearchSysCache(PROCOID, ObjectIdGetDatum(funcid), 0, 0, 0);
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("function \"%u\" does not exist", funcid)));
+
+ functup = (Form_pg_proc) GETSTRUCT(tp);
+
+ memset(fcu->fs, 0, sizeof(*fcu->fs));
+ fcu->fs->f_key = key;
+ fcu->fs->f_name = pstrdup(NameStr(functup->proname));
+ fcu->fs->f_schema = get_namespace_name(functup->pronamespace);
+ fcu->fs->f_nargs = functup->pronargs;
+
+ ReleaseSysCache(tp);
+ }
+
+ /* Make this function the current */
+ current_function_parent = funcid;
/* save stats for this function, later used to compensate for recursion */
- fcu->save_f_time = htabent->f_counts.f_time;
+ fcu->save_counts = fcu->fs->counts;
/* 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);
+
+ MemoryContextSwitchTo(oldcontext);
}
/*
@@ -1452,13 +1647,12 @@ find_funcstat_entry(Oid func_id)
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)
+ if (fcu->fs == NULL)
return;
/* total elapsed time in this function call */
@@ -1481,13 +1675,17 @@ pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
* similar kluge for self time, since that already excludes any recursive
* calls.)
*/
- INSTR_TIME_ADD(f_total, fcu->save_f_time);
+ INSTR_TIME_ADD(f_total, fcu->save_counts.f_time);
/* update counters in function stats table */
if (finalize)
- fs->f_numcalls++;
- fs->f_time = f_total;
- INSTR_TIME_ADD(fs->f_time_self, f_self);
+ fcu->fs->counts.f_numcalls++;
+
+ fcu->fs->counts.f_time = f_total;
+ INSTR_TIME_ADD(fcu->fs->counts.f_time_self, f_self);
+
+ /* restore the previous parent */
+ current_function_parent = fcu->fs->f_key.f_parent;
/* indicate that we have something to send */
have_function_stats = true;
@@ -1542,6 +1740,60 @@ pgstat_initstats(Relation rel)
}
/*
+ * new_tsa_entry - fill in PgStat_TableStatus structure.
+ */
+PgStat_TableStatus *
+new_tsa_entry(TabStatusArray *tsa, Oid rel_id, bool isshared)
+{
+ HeapTuple tp;
+ MemoryContext oldcontext;
+ Form_pg_class reltup;
+
+ PgStat_TableStatus *entry = &tsa->tsa_entries[tsa->tsa_used++];
+ entry->t_id = rel_id;
+ entry->t_shared = isshared;
+ entry->t_name = NULL;
+ entry->t_schema = NULL;
+ entry->t_relkind = ' ';
+
+ if (rel_id < FirstNormalObjectId)
+ {
+ /*
+ * Skip name lookups for system tables. As a sideeffect this also
+ * takes care of the cases where pgstat_initstats is called without
+ * a transaction (e.g. autovacuum launcher or pg_hba role check).
+ */
+ entry->t_system = TRUE;
+ return entry;
+ }
+
+ tp = SearchSysCache(RELOID, ObjectIdGetDatum(rel_id), 0, 0, 0);
+
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("relation \"%u\" does not exist", rel_id)));
+
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+ reltup = (Form_pg_class) GETSTRUCT(tp);
+
+ entry->t_name = pstrdup(NameStr(reltup->relname));
+ entry->t_relkind = reltup->relkind;
+
+ /* Additional check for system objects */
+ if (reltup->relnamespace == PG_CATALOG_NAMESPACE
+ || reltup->relnamespace == PG_TOAST_NAMESPACE)
+ entry->t_system = TRUE;
+ else
+ entry->t_schema = get_namespace_name(reltup->relnamespace);
+
+ MemoryContextSwitchTo(oldcontext);
+ ReleaseSysCache(tp);
+
+ return entry;
+}
+
+/*
* get_tabstat_entry - find or create a PgStat_TableStatus entry for rel
*/
static PgStat_TableStatus *
@@ -1572,10 +1824,7 @@ get_tabstat_entry(Oid rel_id, bool isshared)
* let's use this one. We assume the entry was already zeroed,
* either at creation or after last use.
*/
- entry = &tsa->tsa_entries[tsa->tsa_used++];
- entry->t_id = rel_id;
- entry->t_shared = isshared;
- return entry;
+ return new_tsa_entry(tsa, rel_id, isshared);
}
}
@@ -1589,13 +1838,7 @@ get_tabstat_entry(Oid rel_id, bool isshared)
else
pgStatTabList = tsa;
- /*
- * Use the first entry of the new TabStatusArray.
- */
- entry = &tsa->tsa_entries[tsa->tsa_used++];
- entry->t_id = rel_id;
- entry->t_shared = isshared;
- return entry;
+ return new_tsa_entry(tsa, rel_id, isshared);
}
/*
@@ -4349,18 +4592,18 @@ pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len)
* If it's a new function entry, initialize counters to the values
* we just got.
*/
- funcentry->f_numcalls = funcmsg->f_numcalls;
- funcentry->f_time = funcmsg->f_time;
- funcentry->f_time_self = funcmsg->f_time_self;
+ funcentry->f_numcalls = funcmsg->f_counts.f_numcalls;
+ funcentry->f_time = funcmsg->f_counts.f_time;
+ funcentry->f_time_self = funcmsg->f_counts.f_time_self;
}
else
{
/*
* Otherwise add the values to the existing entry.
*/
- funcentry->f_numcalls += funcmsg->f_numcalls;
- funcentry->f_time += funcmsg->f_time;
- funcentry->f_time_self += funcmsg->f_time_self;
+ funcentry->f_numcalls += funcmsg->f_counts.f_numcalls;
+ INSTR_TIME_ADD(funcentry->f_time, funcmsg->f_counts.f_time);
+ INSTR_TIME_ADD(funcentry->f_time_self, funcmsg->f_counts.f_time_self);
}
}
}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a07661f..b7c102f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3783,6 +3783,11 @@ PostgresMain(int argc, char *argv[], const char *username)
debug_query_string = NULL;
/*
+ * Reset the current function parent.
+ */
+ current_function_parent = InvalidOid;
+
+ /*
* Abort the current transaction in order to recover.
*/
AbortCurrentTransaction();
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f811245..a245a31 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -438,7 +438,7 @@ pg_stat_get_function_time(PG_FUNCTION_ARGS)
if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
PG_RETURN_NULL();
- PG_RETURN_INT64(funcentry->f_time);
+ PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->f_time));
}
Datum
@@ -449,7 +449,7 @@ pg_stat_get_function_self_time(PG_FUNCTION_ARGS)
if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
PG_RETURN_NULL();
- PG_RETURN_INT64(funcentry->f_time_self);
+ PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->f_time_self));
}
Datum
@@ -1487,7 +1487,7 @@ pg_stat_get_xact_function_calls(PG_FUNCTION_ARGS)
if ((funcentry = find_funcstat_entry(funcid)) == NULL)
PG_RETURN_NULL();
- PG_RETURN_INT64(funcentry->f_counts.f_numcalls);
+ PG_RETURN_INT64(funcentry->counts.f_numcalls);
}
Datum
@@ -1498,7 +1498,7 @@ pg_stat_get_xact_function_time(PG_FUNCTION_ARGS)
if ((funcentry = find_funcstat_entry(funcid)) == NULL)
PG_RETURN_NULL();
- PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->f_counts.f_time));
+ PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->counts.f_time));
}
Datum
@@ -1509,7 +1509,7 @@ pg_stat_get_xact_function_self_time(PG_FUNCTION_ARGS)
if ((funcentry = find_funcstat_entry(funcid)) == NULL)
PG_RETURN_NULL();
- PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->f_counts.f_time_self));
+ PG_RETURN_INT64(INSTR_TIME_GET_MICROSEC(funcentry->counts.f_time_self));
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 92391ed..7f1dfea 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -412,6 +412,9 @@ bool log_statement_stats = false; /* this is sort of all three
* above together */
bool log_btree_build_stats = false;
+bool log_usage_stats = false;
+bool log_function_calls = false;
+
bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
@@ -976,6 +979,22 @@ static struct config_bool ConfigureNamesBool[] =
false,
check_log_stats, NULL, NULL
},
+ {
+ {"log_usage_stats", PGC_SUSET, STATS_MONITORING,
+ gettext_noop("Writes table and function access statistics to the server log."),
+ NULL
+ },
+ &log_usage_stats,
+ false, NULL, NULL
+ },
+ {
+ {"log_function_calls", PGC_SUSET, STATS_MONITORING,
+ gettext_noop("Writes top level function call stats to the server log."),
+ NULL
+ },
+ &log_function_calls,
+ false, NULL, NULL
+ },
#ifdef BTREE_BUILD_STATS
{
{"log_btree_build_stats", PGC_SUSET, DEVELOPER_OPTIONS,
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f04be95..1eca1f2 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -134,6 +134,10 @@ typedef struct PgStat_TableStatus
{
Oid t_id; /* table's OID */
bool t_shared; /* is it a shared catalog? */
+ bool t_system; /* is it a system table? */
+ char t_relkind; /* table, index, ... */
+ char *t_schema;
+ char *t_name;
struct PgStat_TableXactStatus *trans; /* lowest subxact's counts */
PgStat_TableCounts t_counts; /* event counts to be sent */
} PgStat_TableStatus;
@@ -395,13 +399,35 @@ typedef struct PgStat_FunctionCounts
} PgStat_FunctionCounts;
/* ----------
- * PgStat_BackendFunctionEntry Entry in backend's per-function hash table
+ * PgStat_MsgFunctionEntry Per-function info in a stat message.
* ----------
*/
-typedef struct PgStat_BackendFunctionEntry
+typedef struct PgStat_MsgFunctionEntry
{
Oid f_id;
PgStat_FunctionCounts f_counts;
+} PgStat_MsgFunctionEntry;
+
+/* Hash key for the function stats */
+typedef struct FunctionStatKey
+{
+ Oid f_id;
+ Oid f_parent;
+} FunctionStatKey;
+
+/* ----------
+ * PgStat_BackendFunctionEntry Entry in backend's per-function hash table
+ * ----------
+ */
+typedef struct PgStat_BackendFunctionEntry
+{
+ FunctionStatKey f_key;
+ int f_nargs;
+ char *f_schema;
+ char *f_name;
+
+ PgStat_FunctionCounts counts;
+
} PgStat_BackendFunctionEntry;
/* ----------
@@ -411,9 +437,7 @@ typedef struct PgStat_BackendFunctionEntry
typedef struct PgStat_FunctionEntry
{
Oid f_id;
- PgStat_Counter f_numcalls;
- PgStat_Counter f_time; /* times in microseconds */
- PgStat_Counter f_time_self;
+ PgStat_FunctionCounts f_counts;
} PgStat_FunctionEntry;
/* ----------
@@ -566,8 +590,8 @@ typedef struct PgStat_StatFuncEntry
PgStat_Counter f_numcalls;
- PgStat_Counter f_time; /* times in microseconds */
- PgStat_Counter f_time_self;
+ instr_time f_time; /* times in microseconds */
+ instr_time f_time_self;
} PgStat_StatFuncEntry;
@@ -647,9 +671,9 @@ typedef struct PgStat_FunctionCallUsage
{
/* Link to function's hashtable entry (must still be there at exit!) */
/* NULL means we are not tracking the current function call */
- PgStat_FunctionCounts *fs;
- /* Total time previously charged to function, as of function start */
- instr_time save_f_time;
+ PgStat_BackendFunctionEntry *fs;
+ PgStat_FunctionCounts counts;
+ PgStat_FunctionCounts save_counts;
/* Backend-wide total time as of function start */
instr_time save_total;
/* system clock as of function start */
@@ -669,6 +693,11 @@ extern char *pgstat_stat_tmpname;
extern char *pgstat_stat_filename;
/*
+ * The current function call parent for dependency tracking.
+ */
+extern Oid current_function_parent;
+
+/*
* BgWriter statistics counters are updated directly by bgwriter and bufmgr
*/
extern PgStat_MsgBgWriter BgWriterStats;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ee52cd7..310ec08 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -198,6 +198,8 @@ extern bool log_planner_stats;
extern bool log_executor_stats;
extern bool log_statement_stats;
extern bool log_btree_build_stats;
+extern bool log_usage_stats;
+extern bool log_function_calls;
extern PGDLLIMPORT bool check_function_bodies;
extern bool default_with_oids;
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers