Re: stored procedure stats in collector - Mailing list pgsql-patches

From Martin Pihlak
Subject Re: stored procedure stats in collector
Date
Msg-id 47E8ED26.8060003@gmail.com
Whole thread Raw
In response to Re: stored procedure stats in collector  (Volkan YAZICI <yazicivo@ttmail.com>)
Responses Re: stored procedure stats in collector  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: stored procedure stats in collector  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-patches
Howdy,

> Moreover, if it'd be possible to specify which specific functions we
> want to try, then that would be awesome as well.
>
> For instance, possible configuration combinations for track_functions
> can be:
>
>   `pl:*'                - Tracks procedural, SQL and C (not internal)

It is probably more efficient to track all functions and then use filters
on the stats view. That way the filters can be arbitrarily complex and
are out of the way of critical code path.

Selective filtering could also be implemented using per-function guc
variables. For example, set "track_functions = none" system wide and
then for specific functions:

alter function foo() set track_functions = "all";

Now I just realized that the current patch doesn't handle this quite
correctly. Modified patch attached.

Hans, this should be equivalent to the TRACK / NOTRACK you proposed? If so,
then we can do without the grammar change and just use the per-function guc.

Regards,
Martin
*** ./doc/src/sgml/config.sgml.orig    2008-03-22 20:15:30.000000000 +0200
--- ./doc/src/sgml/config.sgml    2008-03-23 15:00:27.000000000 +0200
***************
*** 3322,3327 ****
--- 3322,3343 ----
        </listitem>
       </varlistentry>

+      <varlistentry id="guc-track-functions" xreflabel="track_functions">
+       <term><varname>track_functions</varname> (<type>string</type>)</term>
+       <indexterm>
+        <primary><varname>track_functions</> configuration parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         Enables tracking of time and cpu usage of stored procedures. Specify
+         <literal>pl</literal> to count only procedural language functions,
+     <literal>all</literal> to also track SQL and C language functions. The
+     default is <literal>none</literal>. Only superusers can change this
+     setting.
+        </para>
+       </listitem>
+      </varlistentry>
+
       <varlistentry id="guc-update-process-title" xreflabel="update_process_title">
        <term><varname>update_process_title</varname> (<type>boolean</type>)</term>
        <indexterm>
*** ./doc/src/sgml/monitoring.sgml.orig    2008-03-23 13:20:25.000000000 +0200
--- ./doc/src/sgml/monitoring.sgml    2008-03-23 14:55:28.000000000 +0200
***************
*** 151,156 ****
--- 151,161 ----
    </para>

    <para>
+    The parameter <xref linkend="guc-track-functions"> enables tracking of
+    stored procedure usage.
+   </para>
+
+   <para>
     Normally these parameters are set in <filename>postgresql.conf</> so
     that they apply to all server processes, but it is possible to turn
     them on or off in individual sessions using the <xref
***************
*** 370,375 ****
--- 375,390 ----
        <entry>Same as <structname>pg_statio_all_sequences</>, except that only
        user sequences are shown.</entry>
       </row>
+
+      <row>
+       <entry><structname>pg_stat_user_functions</></entry>
+       <entry>For all called functions, function OID, schema, name, number
+       of calls, total time, total cpu, self time and cpu. Self time is the
+       amount of time spent in the function itself, total time includes the
+       time spent in child functions. Time values in milliseconds.
+      </entry>
+      </row>
+
      </tbody>
     </tgroup>
    </table>
***************
*** 655,660 ****
--- 670,733 ----
       </row>

       <row>
+       <entry><literal><function>pg_stat_get_function_calls</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        Number of times the function has been called.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_rtime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        Total wall clock time spent in the function. In microseconds, includes
+        the time spent by child functions.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_stime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        Total system mode CPU time spent in the function.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_utime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        Total user mode CPU time spent in the function.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_self_rtime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        Time spent by only this function. Time spent in child functions
+        is excluded.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_self_stime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        System mode CPU spent by this function.
+       </entry>
+      </row>
+
+      <row>
+       <entry><literal><function>pg_stat_get_function_self_utime</function>(<type>oid</type>)</literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+        User mode CPU spent by this function.
+       </entry>
+      </row>
+
+      <row>
        <entry><literal><function>pg_stat_get_backend_idset</function>()</literal></entry>
        <entry><type>setof integer</type></entry>
        <entry>
*** ./src/backend/catalog/system_views.sql.orig    2008-03-20 10:13:23.000000000 +0200
--- ./src/backend/catalog/system_views.sql    2008-03-22 16:27:38.000000000 +0200
***************
*** 376,381 ****
--- 376,395 ----
              pg_stat_get_db_tuples_deleted(D.oid) AS tup_deleted
      FROM pg_database D;

+ CREATE OR REPLACE VIEW pg_stat_user_functions AS
+     SELECT
+             P.oid AS funcid,
+             N.nspname AS schemaname,
+             P.proname AS funcname,
+             pg_stat_get_function_calls(P.oid) AS calls,
+             pg_stat_get_function_rtime(P.oid)/1000 AS total_time,
+             (pg_stat_get_function_utime(P.oid) + pg_stat_get_function_stime(P.oid)) /1000 AS total_cpu,
+             pg_stat_get_function_self_rtime(P.oid)/1000 AS self_time,
+             (pg_stat_get_function_self_utime(P.oid) + pg_stat_get_function_self_stime(P.oid)) /1000 AS self_cpu
+     FROM    pg_proc P LEFT JOIN pg_namespace N ON (N.oid = P.pronamespace)
+     WHERE
+            pg_stat_get_function_calls(P.oid) IS NOT NULL;
+
  CREATE VIEW pg_stat_bgwriter AS
      SELECT
          pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
*** ./src/backend/commands/trigger.c.orig    2008-03-20 10:15:41.000000000 +0200
--- ./src/backend/commands/trigger.c    2008-03-23 18:42:39.000000000 +0200
***************
*** 13,18 ****
--- 13,19 ----
   */
  #include "postgres.h"

+ #include "pgstat.h"
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/xact.h"
***************
*** 1541,1546 ****
--- 1542,1548 ----
                      MemoryContext per_tuple_context)
  {
      FunctionCallInfoData fcinfo;
+     PgStat_FunctionCallUsage fcusage;
      Datum        result;
      MemoryContext oldContext;

***************
*** 1574,1580 ****
--- 1576,1584 ----
       */
      InitFunctionCallInfoData(fcinfo, finfo, 0, (Node *) trigdata, NULL);

+     pgstat_init_function_usage(&fcinfo, &fcusage);
      result = FunctionCallInvoke(&fcinfo);
+     pgstat_update_function_usage(&fcusage, TRUE);

      MemoryContextSwitchTo(oldContext);

*** ./src/backend/commands/vacuum.c.orig    2008-03-21 19:26:48.000000000 +0200
--- ./src/backend/commands/vacuum.c    2008-03-22 11:21:08.000000000 +0200
***************
*** 319,325 ****
       * in autovacuum --- autovacuum.c does this for itself.
       */
      if (vacstmt->vacuum && !IsAutoVacuumWorkerProcess())
!         pgstat_vacuum_tabstat();

      /*
       * Create special memory context for cross-transaction storage.
--- 319,325 ----
       * in autovacuum --- autovacuum.c does this for itself.
       */
      if (vacstmt->vacuum && !IsAutoVacuumWorkerProcess())
!         pgstat_vacuum_stat();

      /*
       * Create special memory context for cross-transaction storage.
*** ./src/backend/executor/execQual.c.orig    2008-03-20 10:18:39.000000000 +0200
--- ./src/backend/executor/execQual.c    2008-03-23 18:43:00.000000000 +0200
***************
*** 36,41 ****
--- 36,42 ----

  #include "postgres.h"

+ #include "pgstat.h"
  #include "access/heapam.h"
  #include "access/nbtree.h"
  #include "catalog/pg_type.h"
***************
*** 1148,1153 ****
--- 1149,1155 ----
      List       *arguments = fcache->args;
      Datum        result;
      FunctionCallInfoData fcinfo;
+     PgStat_FunctionCallUsage fcusage;
      ReturnSetInfo rsinfo;        /* for functions returning sets */
      ExprDoneCond argDone;
      bool        hasSetArg;
***************
*** 1206,1211 ****
--- 1208,1215 ----
          rsinfo.setDesc = NULL;
      }

+     pgstat_init_function_usage(&fcinfo, &fcusage);
+
      /*
       * now return the value gotten by calling the function manager, passing
       * the function the evaluated parameter values.
***************
*** 1316,1321 ****
--- 1320,1326 ----
               * argument.
               */
          }
+         pgstat_update_function_usage(&fcusage, *isDone == ExprEndResult);
      }
      else
      {
***************
*** 1350,1355 ****
--- 1355,1361 ----
          fcinfo.isnull = false;
          result = FunctionCallInvoke(&fcinfo);
          *isNull = fcinfo.isnull;
+         pgstat_update_function_usage(&fcusage, TRUE);
      }

      return result;
***************
*** 1370,1375 ****
--- 1376,1382 ----
      ListCell   *arg;
      Datum        result;
      FunctionCallInfoData fcinfo;
+     PgStat_FunctionCallUsage fcusage;
      int            i;

      /* Guard against stack overflow due to overly complex expressions */
***************
*** 1392,1397 ****
--- 1399,1405 ----
      }

      InitFunctionCallInfoData(fcinfo, &(fcache->func), i, NULL, NULL);
+     pgstat_init_function_usage(&fcinfo, &fcusage);

      /*
       * If function is strict, and there are any NULL arguments, skip calling
***************
*** 1412,1417 ****
--- 1420,1427 ----
      result = FunctionCallInvoke(&fcinfo);
      *isNull = fcinfo.isnull;

+     pgstat_update_function_usage(&fcusage, TRUE);
+
      return result;
  }

***************
*** 1435,1440 ****
--- 1445,1451 ----
      bool        returnsTuple;
      bool        returnsSet = false;
      FunctionCallInfoData fcinfo;
+     PgStat_FunctionCallUsage fcusage;
      ReturnSetInfo rsinfo;
      HeapTupleData tmptup;
      MemoryContext callerContext;
***************
*** 1535,1540 ****
--- 1546,1553 ----
          direct_function_call = false;
      }

+     pgstat_init_function_usage(&fcinfo, &fcusage);
+
      /*
       * Switch to short-lived context for calling the function or expression.
       */
***************
*** 1685,1690 ****
--- 1698,1705 ----
          first_time = false;
      }

+     pgstat_update_function_usage(&fcusage, TRUE);
+
  no_function_result:

      /*
*** ./src/backend/postmaster/autovacuum.c.orig    2008-03-21 19:26:40.000000000 +0200
--- ./src/backend/postmaster/autovacuum.c    2008-03-22 11:21:24.000000000 +0200
***************
*** 1855,1861 ****
       * want to do this exactly once per DB-processing cycle, even if we find
       * nothing worth vacuuming in the database.
       */
!     pgstat_vacuum_tabstat();

      /*
       * Find the pg_database entry and select the default freeze_min_age. We
--- 1855,1861 ----
       * want to do this exactly once per DB-processing cycle, even if we find
       * nothing worth vacuuming in the database.
       */
!     pgstat_vacuum_stat();

      /*
       * Find the pg_database entry and select the default freeze_min_age. We
*** ./src/backend/postmaster/pgstat.c.orig    2008-03-20 10:29:52.000000000 +0200
--- ./src/backend/postmaster/pgstat.c    2008-03-23 18:43:33.000000000 +0200
***************
*** 42,47 ****
--- 42,48 ----
  #include "access/twophase_rmgr.h"
  #include "access/xact.h"
  #include "catalog/pg_database.h"
+ #include "catalog/pg_proc.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
  #include "libpq/pqsignal.h"
***************
*** 88,93 ****
--- 89,95 ----
   */
  #define PGSTAT_DB_HASH_SIZE        16
  #define PGSTAT_TAB_HASH_SIZE    512
+ #define PGSTAT_FUNCTION_HASH_SIZE    512


  /* ----------
***************
*** 96,101 ****
--- 98,104 ----
   */
  bool        pgstat_track_activities = false;
  bool        pgstat_track_counts = false;
+ int            pgstat_track_functions = 0;

  /*
   * BgWriter global statistics counters (unused in other processes).
***************
*** 138,143 ****
--- 141,150 ----

  static TabStatusArray *pgStatTabList = NULL;

+ /* Function stat entries */
+ HTAB *pgStatFunctions = NULL;
+
+
  /*
   * Tuple insertion/deletion counts for an open transaction can't be propagated
   * into PgStat_TableStatus counters until we know if it is going to commit
***************
*** 184,189 ****
--- 191,200 ----
  static volatile bool need_exit = false;
  static volatile bool need_statwrite = false;

+ /* Helper variables for function self time calculations. */
+ static uint64 total_stime = 0;
+ static uint64 total_utime = 0;
+ static uint64 total_rtime = 0;

  /* ----------
   * Local function forward declarations
***************
*** 205,210 ****
--- 216,222 ----
  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 PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
***************
*** 216,221 ****
--- 228,235 ----

  static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len);
  static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len);
+ static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len);
+ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
  static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len);
  static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len);
  static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len);
***************
*** 600,615 ****


  /* ----------
!  * pgstat_report_tabstat() -
   *
   *    Called from tcop/postgres.c to send the so far collected per-table
!  *    access statistics to the collector.  Note that this is called only
!  *    when not within a transaction, so it is fair to use transaction stop
!  *    time as an approximation of current time.
   * ----------
   */
  void
! pgstat_report_tabstat(bool force)
  {
      /* we assume this inits to all zeroes: */
      static const PgStat_TableCounts all_zeroes;
--- 614,629 ----


  /* ----------
!  * pgstat_report_stat() -
   *
   *    Called from tcop/postgres.c to send the so far collected per-table
!  *    and function access statistics to the collector.  Note that this is
!  *    called only when not within a transaction, so it is fair to use
!  *    transaction stop time as an approximation of current time.
   * ----------
   */
  void
! pgstat_report_stat(bool force)
  {
      /* we assume this inits to all zeroes: */
      static const PgStat_TableCounts all_zeroes;
***************
*** 695,704 ****
          pgstat_send_tabstat(®ular_msg);
      if (shared_msg.m_nentries > 0)
          pgstat_send_tabstat(&shared_msg);
  }

  /*
!  * Subroutine for pgstat_report_tabstat: finish and send a tabstat message
   */
  static void
  pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg)
--- 709,721 ----
          pgstat_send_tabstat(®ular_msg);
      if (shared_msg.m_nentries > 0)
          pgstat_send_tabstat(&shared_msg);
+
+     /* Now, send the functions */
+     pgstat_send_funcstats();
  }

  /*
!  * Subroutine for pgstat_report_stat: finish and send a tabstat message
   */
  static void
  pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg)
***************
*** 735,755 ****
      pgstat_send(tsmsg, len);
  }


  /* ----------
!  * pgstat_vacuum_tabstat() -
   *
   *    Will tell the collector about objects he can get rid of.
   * ----------
   */
  void
! pgstat_vacuum_tabstat(void)
  {
      HTAB       *htab;
      PgStat_MsgTabpurge msg;
      HASH_SEQ_STATUS hstat;
      PgStat_StatDBEntry *dbentry;
      PgStat_StatTabEntry *tabentry;
      int            len;

      if (pgStatSock < 0)
--- 752,812 ----
      pgstat_send(tsmsg, len);
  }

+ /*
+  * Subroutine for pgstat_report_stat: populate and send a function stat message.
+  */
+ void
+ pgstat_send_funcstats(void)
+ {
+     PgStat_MsgFuncstat msg;
+     PgStat_FunctionEntry *entry;
+     HASH_SEQ_STATUS fstat;
+
+     if (pgStatFunctions == NULL)
+         return;
+
+     pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_FUNCSTAT);
+     msg.m_databaseid = MyDatabaseId;
+     msg.m_nentries = 0;
+
+     hash_seq_init(&fstat, pgStatFunctions);
+     while ((entry = (PgStat_FunctionEntry *)hash_seq_search(&fstat)) != NULL)
+     {
+             msg.m_entry[msg.m_nentries] = *entry;
+
+             /* clear the entry, but retain Oid */
+             memset(entry, 0, sizeof(PgStat_FunctionEntry));
+             entry->f_id = msg.m_entry[msg.m_nentries].f_id;
+
+             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;
+             }
+     }
+
+     if (msg.m_nentries > 0)
+         pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
+             msg.m_nentries * sizeof(PgStat_FunctionEntry));
+ }

  /* ----------
!  * pgstat_vacuum_stat() -
   *
   *    Will tell the collector about objects he can get rid of.
   * ----------
   */
  void
! pgstat_vacuum_stat(void)
  {
      HTAB       *htab;
      PgStat_MsgTabpurge msg;
+     PgStat_MsgFuncpurge f_msg;
      HASH_SEQ_STATUS hstat;
      PgStat_StatDBEntry *dbentry;
      PgStat_StatTabEntry *tabentry;
+     PgStat_StatFuncEntry *funcentry;
      int            len;

      if (pgStatSock < 0)
***************
*** 854,861 ****

      /* Clean up */
      hash_destroy(htab);
- }


  /* ----------
   * pgstat_collect_oids() -
--- 911,965 ----

      /* Clean up */
      hash_destroy(htab);

+     /*
+      * Done with the tables, now repeat the above steps for functions.
+      */
+     htab = pgstat_collect_oids(ProcedureRelationId);
+
+     pgstat_setheader(&f_msg.m_hdr, PGSTAT_MTYPE_FUNCPURGE);
+     f_msg.m_nentries = 0;
+     f_msg.m_databaseid = MyDatabaseId;
+
+     /*
+      * Check for all functions listed in stats hashtable if they still exist.
+      */
+     hash_seq_init(&hstat, dbentry->functions);
+     while ((funcentry = (PgStat_StatFuncEntry *) hash_seq_search(&hstat)) != NULL)
+     {
+         Oid            funcid = funcentry->functionid;
+
+         CHECK_FOR_INTERRUPTS();
+
+         if (hash_search(htab, (void *) &funcid, HASH_FIND, NULL) != NULL)
+             /* still exists, move on */
+             continue;
+
+         f_msg.m_functionid[f_msg.m_nentries++] = funcid;
+
+         /*
+          * If the message is full, send it out and reinitialize to empty
+          */
+         if (f_msg.m_nentries >= PGSTAT_NUM_TABPURGE)
+         {
+             len = offsetof(PgStat_MsgFuncpurge, m_functionid[0])
+                 +f_msg.m_nentries * sizeof(Oid);
+
+             pgstat_send(&f_msg, len);
+             f_msg.m_nentries = 0;
+         }
+     }
+
+     if (f_msg.m_nentries > 0)
+     {
+         len = offsetof(PgStat_MsgFuncpurge, m_functionid[0])
+             +f_msg.m_nentries * sizeof(Oid);
+
+         pgstat_send(&f_msg, len);
+     }
+
+     hash_destroy(htab);
+ }

  /* ----------
   * pgstat_collect_oids() -
***************
*** 905,911 ****
   *
   *    Tell the collector that we just dropped a database.
   *    (If the message gets lost, we will still clean the dead DB eventually
!  *    via future invocations of pgstat_vacuum_tabstat().)
   * ----------
   */
  void
--- 1009,1015 ----
   *
   *    Tell the collector that we just dropped a database.
   *    (If the message gets lost, we will still clean the dead DB eventually
!  *    via future invocations of pgstat_vacuum_stat().)
   * ----------
   */
  void
***************
*** 927,936 ****
   *
   *    Tell the collector that we just dropped a relation.
   *    (If the message gets lost, we will still clean the dead entry eventually
!  *    via future invocations of pgstat_vacuum_tabstat().)
   *
   *    Currently not used for lack of any good place to call it; we rely
!  *    entirely on pgstat_vacuum_tabstat() to clean out stats for dead rels.
   * ----------
   */
  #ifdef NOT_USED
--- 1031,1040 ----
   *
   *    Tell the collector that we just dropped a relation.
   *    (If the message gets lost, we will still clean the dead entry eventually
!  *    via future invocations of pgstat_vacuum_stat().)
   *
   *    Currently not used for lack of any good place to call it; we rely
!  *    entirely on pgstat_vacuum_stat() to clean out stats for dead rels.
   * ----------
   */
  #ifdef NOT_USED
***************
*** 1073,1078 ****
--- 1177,1294 ----
      pgstat_send(&msg, sizeof(msg));
  }

+ /*
+  * Initialize function call usage structures. Called by the executor before
+  * invoking a function.
+  */
+ void
+ pgstat_init_function_usage(FunctionCallInfoData *fcinfo, PgStat_FunctionCallUsage *fcu)
+ {
+     HASHCTL        hash_ctl;
+     bool         found;
+
+     fcu->fn_oid = InvalidOid;
+
+     if (!pgstat_track_functions || !fcinfo->flinfo) {
+         /* We are not collecting or we don't have oid of the function (got rewritten?)*/
+         return;
+     }
+
+     if (!fcinfo->flinfo->fn_stats) {
+         /* stats not needed */
+         return;
+     }
+
+     gettimeofday(&fcu->t_start, NULL);
+     getrusage(RUSAGE_SELF, &fcu->r_start);
+     fcu->rtime_us = fcu->self_rtime = 0;
+     fcu->utime_us = fcu->self_utime = 0;
+     fcu->stime_us = fcu->self_stime = 0;
+     fcu->fn_oid = fcinfo->flinfo->fn_oid;
+
+     if (!pgStatFunctions) {
+         /* First time - initialize function stat table */
+         memset(&hash_ctl, 0, sizeof(hash_ctl));
+         hash_ctl.keysize = sizeof(Oid);
+         hash_ctl.entrysize = sizeof(PgStat_FunctionEntry);
+         hash_ctl.hash = oid_hash;
+         pgStatFunctions = hash_create("Function stat entries",
+                                     PGSTAT_FUNCTION_HASH_SIZE,
+                                     &hash_ctl,
+                                     HASH_ELEM | HASH_FUNCTION );
+     }
+
+     /* get the stats entry for this function, create if necessary */
+     fcu->fs = hash_search(pgStatFunctions, &fcu->fn_oid, HASH_ENTER, &found);
+     if (!found) {
+         memset(fcu->fs, 0, sizeof(PgStat_FunctionEntry));
+         fcu->fs->f_id = fcu->fn_oid;
+     }
+
+     /* save stats for this function, later used to compensate for recursion */
+     fcu->save_fs = *fcu->fs;
+
+     /* save totals for 'self' calculation */
+     fcu->save_stime = total_stime;
+     fcu->save_utime = total_utime;
+     fcu->save_rtime = total_rtime;
+ }
+
+ /*
+  * Calculate function call usage and update stat counters. Called by the
+  * executor when its done with the function. Might be called several times
+  * by set returning functions, last invocation should then have finalize
+  * set to TRUE.
+  */
+ void
+ pgstat_update_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
+ {
+     struct timeval t1;
+     struct rusage r1;
+
+     if (!pgstat_track_functions || fcu->fn_oid == InvalidOid)
+         return;
+
+     getrusage(RUSAGE_SELF, &r1);
+     gettimeofday(&t1, NULL);
+
+     /* total elapsed */
+     fcu->utime_us += r1.ru_utime.tv_sec*1000000 + r1.ru_utime.tv_usec -
+                     (fcu->r_start.ru_utime.tv_sec*1000000 + fcu->r_start.ru_utime.tv_usec);
+     fcu->stime_us += r1.ru_stime.tv_sec*1000000 + r1.ru_stime.tv_usec -
+                     (fcu->r_start.ru_stime.tv_sec*1000000 + fcu->r_start.ru_stime.tv_usec);
+     fcu->rtime_us += t1.tv_sec*1000000 + t1.tv_usec -
+                     (fcu->t_start.tv_sec*1000000 + fcu->t_start.tv_usec);
+
+     /* extract self usage */
+     fcu->self_stime = (fcu->save_stime + fcu->stime_us) - total_stime;
+     fcu->self_utime = (fcu->save_utime + fcu->utime_us) - total_utime;
+     fcu->self_rtime = (fcu->save_rtime + fcu->rtime_us) - total_rtime;
+
+     /* subtract recursive calls of myself */
+     fcu->utime_us -= fcu->fs->f_utime - fcu->save_fs.f_utime;
+     fcu->stime_us -= fcu->fs->f_stime - fcu->save_fs.f_stime;
+     fcu->rtime_us -= fcu->fs->f_rtime - fcu->save_fs.f_rtime;
+
+     elog(DEBUG5, "pgstat_update_function_usage: rtime start=%llu end=%llu\n", fcu->save_fs.f_rtime,
fcu->fs->f_rtime);
+
+     total_stime += fcu->self_stime;
+     total_utime += fcu->self_utime;
+     total_rtime += fcu->self_rtime;
+
+     if (finalize)
+         pgstat_count_function_calls(fcu->fs);
+
+     /* update the calculated values in function stats table */
+     pgstat_count_function_rtime(fcu->fs, fcu->rtime_us, fcu->self_rtime);
+     pgstat_count_function_utime(fcu->fs, fcu->utime_us, fcu->self_utime);
+     pgstat_count_function_stime(fcu->fs, fcu->stime_us, fcu->self_stime);
+
+     elog(DEBUG5, "pgstat_update_function_usage %u %s: real=%llu user=%llu sys=%llu us [self %llu %llu %llu us]",
+         fcu->fn_oid, finalize ? "final" : "contemporary",
+         fcu->rtime_us, fcu->utime_us, fcu->stime_us,
+         fcu->self_rtime, fcu->self_utime, fcu->self_stime);
+ }

  /* ----------
   * pgstat_initstats() -
***************
*** 1661,1666 ****
--- 1877,1909 ----
      return NULL;
  }

+ /* ----------
+  * pgstat_fetch_stat_funcentry() -
+  *
+  *    Support function for the SQL-callable pgstat* functions. Returns
+  *    the collected statistics for one function or NULL.
+  * ----------
+  */
+ PgStat_StatFuncEntry *
+ pgstat_fetch_stat_funcentry(Oid func_id)
+ {
+     PgStat_StatDBEntry *dbentry;
+     PgStat_StatFuncEntry *funcentry = NULL;
+
+     /* load the stats file if needed */
+     backend_read_statsfile();
+
+     /* Lookup our database, then find the requested function.  */
+     dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId);
+     if (dbentry != NULL && dbentry->functions != NULL)
+     {
+         funcentry = (PgStat_StatFuncEntry *) hash_search(dbentry->functions,
+                                                        (void *) &func_id,
+                                                        HASH_FIND, NULL);
+     }
+
+     return funcentry;
+ }

  /* ----------
   * pgstat_fetch_stat_beentry() -
***************
*** 1861,1867 ****
  {
      volatile PgBackendStatus *beentry = MyBEEntry;

!     pgstat_report_tabstat(true);

      /*
       * Clear my status entry, following the protocol of bumping st_changecount
--- 2104,2110 ----
  {
      volatile PgBackendStatus *beentry = MyBEEntry;

!     pgstat_report_stat(true);

      /*
       * Clear my status entry, following the protocol of bumping st_changecount
***************
*** 2343,2348 ****
--- 2586,2599 ----
                      pgstat_recv_tabpurge((PgStat_MsgTabpurge *) &msg, len);
                      break;

+                  case PGSTAT_MTYPE_FUNCSTAT:
+                      pgstat_recv_funcstat((PgStat_MsgFuncstat *) &msg, len);
+                      break;
+
+                 case PGSTAT_MTYPE_FUNCPURGE:
+                     pgstat_recv_funcpurge((PgStat_MsgFuncpurge *) &msg, len);
+                     break;
+
                  case PGSTAT_MTYPE_DROPDB:
                      pgstat_recv_dropdb((PgStat_MsgDropdb *) &msg, len);
                      break;
***************
*** 2446,2451 ****
--- 2697,2703 ----
          HASHCTL        hash_ctl;

          result->tables = NULL;
+         result->functions = NULL;
          result->n_xact_commit = 0;
          result->n_xact_rollback = 0;
          result->n_blocks_fetched = 0;
***************
*** 2465,2470 ****
--- 2717,2728 ----
                                       PGSTAT_TAB_HASH_SIZE,
                                       &hash_ctl,
                                       HASH_ELEM | HASH_FUNCTION);
+
+         hash_ctl.entrysize = sizeof(PgStat_FunctionEntry);
+         result->functions = hash_create("Per-database function",
+                                      PGSTAT_TAB_HASH_SIZE,
+                                      &hash_ctl,
+                                      HASH_ELEM | HASH_FUNCTION);
      }

      return result;
***************
*** 2482,2489 ****
--- 2740,2749 ----
  {
      HASH_SEQ_STATUS hstat;
      HASH_SEQ_STATUS tstat;
+     HASH_SEQ_STATUS fstat;
      PgStat_StatDBEntry *dbentry;
      PgStat_StatTabEntry *tabentry;
+     PgStat_StatFuncEntry *funcentry;
      FILE       *fpout;
      int32        format_id;

***************
*** 2536,2541 ****
--- 2796,2814 ----
          }

          /*
+          * Walk through the database's function stats table.
+          */
+         hash_seq_init(&fstat, dbentry->functions);
+         while ((funcentry = (PgStat_StatFuncEntry *) hash_seq_search(&fstat)) != NULL)
+         {
+             if (funcentry->numcalls > 0)
+             {
+                 fputc('F', fpout);
+                 fwrite(funcentry, sizeof(PgStat_StatFuncEntry), 1, fpout);
+             }
+         }
+
+         /*
           * Mark the end of this DB
           */
          fputc('d', fpout);
***************
*** 2590,2598 ****
--- 2863,2874 ----
      PgStat_StatDBEntry dbbuf;
      PgStat_StatTabEntry *tabentry;
      PgStat_StatTabEntry tabbuf;
+     PgStat_StatFuncEntry funcbuf;
+     PgStat_StatFuncEntry *funcentry;
      HASHCTL        hash_ctl;
      HTAB       *dbhash;
      HTAB       *tabhash = NULL;
+     HTAB       *funchash = NULL;
      FILE       *fpin;
      int32        format_id;
      bool        found;
***************
*** 2686,2691 ****
--- 2962,2968 ----

                  memcpy(dbentry, &dbbuf, sizeof(PgStat_StatDBEntry));
                  dbentry->tables = NULL;
+                 dbentry->functions = NULL;

                  /*
                   * Don't collect tables if not the requested DB (or the
***************
*** 2708,2718 ****
--- 2985,3001 ----
                                                &hash_ctl,
                                     HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);

+                 hash_ctl.entrysize = sizeof(PgStat_FunctionEntry);
+                 dbentry->functions = hash_create("Per-database function",
+                                               PGSTAT_FUNCTION_HASH_SIZE,
+                                               &hash_ctl,
+                                    HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
                  /*
                   * Arrange that following 'T's add entries to this database's
                   * tables hash table.
                   */
                  tabhash = dbentry->tables;
+                 funchash = dbentry->functions;
                  break;

                  /*
***************
*** 2720,2725 ****
--- 3003,3009 ----
                   */
              case 'd':
                  tabhash = NULL;
+                 funchash = NULL;
                  break;

                  /*
***************
*** 2755,2760 ****
--- 3039,3076 ----
                  break;

                  /*
+                  * 'F'    A PgStat_FunctionEntry follows
+                  */
+             case 'F':
+                 if (fread(&funcbuf, 1, sizeof(PgStat_StatFuncEntry),
+                           fpin) != sizeof(PgStat_StatFuncEntry))
+                 {
+                     ereport(pgStatRunningInCollector ? LOG : WARNING,
+                             (errmsg("corrupted pgstat.stat file")));
+                     goto done;
+                 }
+
+                 /*
+                  * Skip if table belongs to a not requested database.
+                  */
+                 if (funchash == NULL)
+                     break;
+
+                 funcentry = (PgStat_StatFuncEntry *) hash_search(funchash,
+                                                     (void *) &funcbuf.functionid,
+                                                          HASH_ENTER, &found);
+
+                 if (found)
+                 {
+                     ereport(pgStatRunningInCollector ? LOG : WARNING,
+                             (errmsg("corrupted pgstat.stat file")));
+                     goto done;
+                 }
+
+                 memcpy(funcentry, &funcbuf, sizeof(funcbuf));
+                 break;
+
+                 /*
                   * 'E'    The EOF marker of a complete stats file.
                   */
              case 'E':
***************
*** 2962,2967 ****
--- 3278,3367 ----
      }
  }

+ /* ----------
+  * pgstat_recv_funcstat() -
+  *
+  *    Count what the backend has done.
+  * ----------
+  */
+ static void
+ pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len)
+ {
+     PgStat_FunctionEntry *funcmsg = &(msg->m_entry[0]);
+     PgStat_StatDBEntry *dbentry;
+     PgStat_StatFuncEntry *funcentry;
+     int            i;
+     bool        found;
+
+     dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
+
+     /*
+      * Process all function entries in the message.
+      */
+     for (i = 0; i < msg->m_nentries; i++)
+     {
+         funcentry = (PgStat_StatFuncEntry *) hash_search(dbentry->functions,
+                                                   (void *) &(funcmsg[i].f_id),
+                                                        HASH_ENTER, &found);
+
+         if (!found)
+         {
+             /*
+              * If it's a new function entry, initialize counters to the values we
+              * just got.
+              */
+             funcentry->numcalls = funcmsg[i].f_numcalls;
+             funcentry->utime = funcmsg[i].f_utime;
+             funcentry->stime = funcmsg[i].f_stime;
+             funcentry->rtime = funcmsg[i].f_rtime;
+             funcentry->utime_self = funcmsg[i].f_utime_self;
+             funcentry->stime_self = funcmsg[i].f_stime_self;
+             funcentry->rtime_self = funcmsg[i].f_rtime_self;
+         }
+         else
+         {
+             /*
+              * Otherwise add the values to the existing entry.
+              */
+             funcentry->numcalls += funcmsg[i].f_numcalls;
+             funcentry->utime += funcmsg[i].f_utime;
+             funcentry->stime += funcmsg[i].f_stime;
+             funcentry->rtime += funcmsg[i].f_rtime;
+             funcentry->utime_self += funcmsg[i].f_utime_self;
+             funcentry->stime_self += funcmsg[i].f_stime_self;
+             funcentry->rtime_self += funcmsg[i].f_rtime_self;
+         }
+     }
+
+ }
+
+ /* ----------
+  * pgstat_recv_funcpurge() -
+  *
+  *    Arrange for dead function removal.
+  * ----------
+  */
+ static void
+ pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len)
+ {
+     PgStat_StatDBEntry *dbentry;
+     int            i;
+
+     dbentry = pgstat_get_db_entry(msg->m_databaseid, false);
+
+     if (!dbentry || !dbentry->tables)
+         return;
+
+     /*
+      * Process all function entries in the message.
+      */
+     for (i = 0; i < msg->m_nentries; i++)
+     {
+         (void) hash_search(dbentry->functions,
+                            (void *) &(msg->m_functionid[i]),
+                            HASH_REMOVE, NULL);
+     }
+ }

  /* ----------
   * pgstat_recv_dropdb() -
***************
*** 2986,2991 ****
--- 3386,3393 ----
      {
          if (dbentry->tables != NULL)
              hash_destroy(dbentry->tables);
+         if (dbentry->functions != NULL)
+             hash_destroy(dbentry->functions);

          if (hash_search(pgStatDBHash,
                          (void *) &(dbentry->databaseid),
***************
*** 3023,3030 ****
--- 3425,3435 ----
       */
      if (dbentry->tables != NULL)
          hash_destroy(dbentry->tables);
+     if (dbentry->functions != NULL)
+         hash_destroy(dbentry->functions);

      dbentry->tables = NULL;
+     dbentry->functions = NULL;
      dbentry->n_xact_commit = 0;
      dbentry->n_xact_rollback = 0;
      dbentry->n_blocks_fetched = 0;
***************
*** 3038,3043 ****
--- 3443,3454 ----
                                    PGSTAT_TAB_HASH_SIZE,
                                    &hash_ctl,
                                    HASH_ELEM | HASH_FUNCTION);
+
+     hash_ctl.entrysize = sizeof(PgStat_FunctionEntry);
+     dbentry->functions = hash_create("Per-database function",
+                                   PGSTAT_FUNCTION_HASH_SIZE,
+                                   &hash_ctl,
+                                   HASH_ELEM | HASH_FUNCTION);
  }

  /* ----------
***************
*** 3170,3172 ****
--- 3581,3584 ----
      globalStats.buf_written_backend += msg->m_buf_written_backend;
      globalStats.buf_alloc += msg->m_buf_alloc;
  }
+
*** ./src/backend/tcop/postgres.c.orig    2008-03-20 10:46:13.000000000 +0200
--- ./src/backend/tcop/postgres.c    2008-03-22 11:39:41.000000000 +0200
***************
*** 3488,3494 ****
              }
              else
              {
!                 pgstat_report_tabstat(false);

                  set_ps_display("idle", false);
                  pgstat_report_activity("<IDLE>");
--- 3488,3494 ----
              }
              else
              {
!                 pgstat_report_stat(false);

                  set_ps_display("idle", false);
                  pgstat_report_activity("<IDLE>");
*** ./src/backend/utils/adt/pgstatfuncs.c.orig    2008-03-20 10:47:44.000000000 +0200
--- ./src/backend/utils/adt/pgstatfuncs.c    2008-03-20 10:51:10.000000000 +0200
***************
*** 38,43 ****
--- 38,51 ----
  extern Datum pg_stat_get_last_analyze_time(PG_FUNCTION_ARGS);
  extern Datum pg_stat_get_last_autoanalyze_time(PG_FUNCTION_ARGS);

+ extern Datum pg_stat_get_function_calls(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_utime(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_stime(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_rtime(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_self_utime(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_self_stime(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_function_self_rtime(PG_FUNCTION_ARGS);
+
  extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
  extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
  extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
***************
*** 324,329 ****
--- 332,420 ----
  }

  Datum
+ pg_stat_get_function_calls(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+     PG_RETURN_INT64(funcentry->numcalls);
+ }
+
+ Datum
+ pg_stat_get_function_utime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->utime);
+ }
+
+ Datum
+ pg_stat_get_function_stime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->stime);
+ }
+
+ Datum
+ pg_stat_get_function_rtime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->rtime);
+ }
+
+ Datum
+ pg_stat_get_function_self_utime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->utime_self);
+ }
+
+ Datum
+ pg_stat_get_function_self_stime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->stime_self);
+ }
+
+ Datum
+ pg_stat_get_function_self_rtime(PG_FUNCTION_ARGS)
+ {
+     Oid    funcid = PG_GETARG_OID(0);
+     PgStat_StatFuncEntry *funcentry;
+
+     if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+         PG_RETURN_NULL();
+
+     PG_RETURN_INT64(funcentry->rtime_self);
+ }
+
+ Datum
  pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
  {
      FuncCallContext *funcctx;
*** ./src/backend/utils/fmgr/fmgr.c.orig    2008-03-20 10:51:21.000000000 +0200
--- ./src/backend/utils/fmgr/fmgr.c    2008-03-25 14:05:38.000000000 +0200
***************
*** 27,32 ****
--- 27,33 ----
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
+ #include "pgstat.h"


  /*
***************
*** 136,141 ****
--- 137,156 ----
  }

  /*
+  * Determine if function call tracking is enabled for this language.
+  */
+ static bool
+ need_function_stats(Oid prolang)
+ {
+     if (prolang == INTERNALlanguageId)
+         return false;
+     if (prolang == ClanguageId || prolang == SQLlanguageId)
+         return (pgstat_track_functions == TRACK_FUNC_ALL);
+
+     return (pgstat_track_functions != TRACK_FUNC_OFF);
+ }
+
+ /*
   * This routine fills a FmgrInfo struct, given the OID
   * of the function to be called.
   *
***************
*** 189,194 ****
--- 204,210 ----
      finfo->fn_extra = NULL;
      finfo->fn_mcxt = mcxt;
      finfo->fn_expr = NULL;        /* caller may set this later */
+     finfo->fn_stats = false;

      if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
      {
***************
*** 273,278 ****
--- 289,295 ----
      }

      finfo->fn_oid = functionId;
+     finfo->fn_stats = need_function_stats(procedureStruct->prolang);
      ReleaseSysCache(procedureTuple);
  }

***************
*** 844,849 ****
--- 861,867 ----
  {
      FmgrInfo    flinfo;            /* lookup info for target function */
      Oid            userid;            /* userid to set, or InvalidOid */
+     Oid            prolang;        /* need this to re-evaluate stats requirements */
      ArrayType  *proconfig;        /* GUC values to set, or NULL */
  };

***************
*** 866,871 ****
--- 884,890 ----
      Oid            save_userid;
      bool        save_secdefcxt;
      volatile int save_nestlevel;
+     PgStat_FunctionCallUsage fcusage;

      if (!fcinfo->flinfo->fn_extra)
      {
***************
*** 893,898 ****
--- 912,919 ----
          if (procedureStruct->prosecdef)
              fcache->userid = procedureStruct->proowner;

+         fcache->prolang = procedureStruct->prolang;
+
          datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig,
                                  &isnull);
          if (!isnull)
***************
*** 938,944 ****
--- 959,970 ----
      {
          fcinfo->flinfo = &fcache->flinfo;

+         /* re-evaluate our need for stats */
+         fcinfo->flinfo->fn_stats = need_function_stats(fcache->prolang);
+
+         pgstat_init_function_usage(fcinfo, &fcusage);
          result = FunctionCallInvoke(fcinfo);
+         pgstat_update_function_usage(&fcusage, true);
      }
      PG_CATCH();
      {
*** ./src/backend/utils/misc/guc.c.orig    2008-03-20 10:54:29.000000000 +0200
--- ./src/backend/utils/misc/guc.c    2008-03-23 14:56:59.000000000 +0200
***************
*** 208,213 ****
--- 208,220 ----
      {NULL, 0}
  };

+ static const struct config_enum_entry track_function_options [] = {
+     {"none", TRACK_FUNC_OFF},
+     {"pl", TRACK_FUNC_PL},
+     {"all", TRACK_FUNC_ALL},
+     {NULL, 0}
+ };
+
  /*
   * GUC option variables that are exported from this module
   */
***************
*** 2488,2493 ****
--- 2495,2508 ----
          LOGSTMT_NONE, log_statement_options, NULL, NULL
      },

+     {
+         {"track_functions", PGC_USERSET, STATS_COLLECTOR,
+             gettext_noop("Collects function-level statistics on database activity."),
+             gettext_noop("Valid values are: pl, all and none.")
+         },
+         &pgstat_track_functions,
+         TRACK_FUNC_OFF, track_function_options, NULL, NULL
+     },



*** ./src/backend/utils/misc/postgresql.conf.sample.orig    2008-03-20 11:00:32.000000000 +0200
--- ./src/backend/utils/misc/postgresql.conf.sample    2008-03-23 14:57:25.000000000 +0200
***************
*** 362,367 ****
--- 362,368 ----

  #track_activities = on
  #track_counts = on
+ #track_functions = none            # none, pl, all
  #update_process_title = on


*** ./src/include/catalog/pg_proc.h.orig    2008-03-20 11:01:01.000000000 +0200
--- ./src/include/catalog/pg_proc.h    2008-03-20 15:21:34.000000000 +0200
***************
*** 2949,2954 ****
--- 2949,2969 ----
  DATA(insert OID = 2859 ( pg_stat_get_buf_alloc            PGNSP PGUID 12 1 0 f f t f s 0 20 "" _null_ _null_ _null_
pg_stat_get_buf_alloc- _null_ _null_ )); 
  DESCR("statistics: number of buffer allocations");

+ DATA(insert OID = 2978 (  pg_stat_get_function_calls PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_calls- _null_ _null_ )); 
+ DESCR("Statistics: Number of function calls");
+ DATA(insert OID = 2979 (  pg_stat_get_function_utime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_utime- _null_ _null_ )); 
+ DESCR("Statistics: User mode cpu usage of function");
+ DATA(insert OID = 2980 (  pg_stat_get_function_stime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_stime- _null_ _null_ )); 
+ DESCR("Statistics: System mode cpu usage of function");
+ DATA(insert OID = 2981 (  pg_stat_get_function_rtime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_rtime- _null_ _null_ )); 
+ DESCR("Statistics: Execution time of function");
+ DATA(insert OID = 2982 (  pg_stat_get_function_self_utime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_self_utime- _null_ _null_ )); 
+ DESCR("Statistics: Self user mode cpu usage of function");
+ DATA(insert OID = 2983 (  pg_stat_get_function_self_stime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_self_stime- _null_ _null_ )); 
+ DESCR("Statistics: Self system mode cpu usage of function");
+ DATA(insert OID = 2984 (  pg_stat_get_function_self_rtime PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_
pg_stat_get_function_self_rtime- _null_ _null_ )); 
+ DESCR("Statistics: Self execution time of function");
+
  DATA(insert OID = 2230 (  pg_stat_clear_snapshot        PGNSP PGUID 12 1 0 f f f f v 0 2278  "" _null_ _null_ _null_
 pg_stat_clear_snapshot - _null_ _null_ )); 
  DESCR("statistics: discard current transaction's statistics snapshot");
  DATA(insert OID = 2274 (  pg_stat_reset                    PGNSP PGUID 12 1 0 f f f f v 0 2278  "" _null_ _null_
_null_   pg_stat_reset - _null_ _null_ )); 
*** ./src/include/fmgr.h.orig    2008-03-20 11:05:48.000000000 +0200
--- ./src/include/fmgr.h    2008-03-20 11:06:20.000000000 +0200
***************
*** 50,55 ****
--- 50,56 ----
      bool        fn_strict;        /* function is "strict" (NULL in => NULL out) */
      bool        fn_retset;        /* function returns a set */
      void       *fn_extra;        /* extra space for use by handler */
+     bool        fn_stats;        /* collect function call stats */
      MemoryContext fn_mcxt;        /* memory context to store fn_extra in */
      fmNodePtr    fn_expr;        /* expression parse tree for call, or NULL */
  } FmgrInfo;
*** ./src/include/pgstat.h.orig    2008-03-20 11:06:43.000000000 +0200
--- ./src/include/pgstat.h    2008-03-23 18:44:09.000000000 +0200
***************
*** 16,21 ****
--- 16,30 ----
  #include "utils/rel.h"
  #include "utils/timestamp.h"

+ #ifdef HAVE_SYS_RESOURCE_H
+ #include <sys/time.h>
+ #include <sys/resource.h>
+ #endif
+
+ #ifndef HAVE_GETRUSAGE
+ #include "rusagestub.h"
+ #endif
+

  /* ----------
   * The types of backend -> collector messages
***************
*** 31,39 ****
      PGSTAT_MTYPE_AUTOVAC_START,
      PGSTAT_MTYPE_VACUUM,
      PGSTAT_MTYPE_ANALYZE,
!     PGSTAT_MTYPE_BGWRITER
  } StatMsgType;

  /* ----------
   * The data type used for counters.
   * ----------
--- 40,56 ----
      PGSTAT_MTYPE_AUTOVAC_START,
      PGSTAT_MTYPE_VACUUM,
      PGSTAT_MTYPE_ANALYZE,
!     PGSTAT_MTYPE_BGWRITER,
!     PGSTAT_MTYPE_FUNCSTAT,
!     PGSTAT_MTYPE_FUNCPURGE
  } StatMsgType;

+ typedef enum {
+     TRACK_FUNC_OFF,
+     TRACK_FUNC_PL,
+     TRACK_FUNC_ALL
+ } TrackFunctionsLevel;
+
  /* ----------
   * The data type used for counters.
   * ----------
***************
*** 173,178 ****
--- 190,214 ----
  } PgStat_TableEntry;

  /* ----------
+  * PgStat_FunctionEntry            Per-function info in a MsgFuncstat
+  *
+  */
+ typedef struct PgStat_FunctionEntry
+ {
+     Oid                f_id;
+
+     PgStat_Counter    f_numcalls;
+
+     PgStat_Counter    f_utime;
+     PgStat_Counter    f_stime;
+     PgStat_Counter    f_rtime;
+
+     PgStat_Counter    f_utime_self;
+     PgStat_Counter    f_stime_self;
+     PgStat_Counter    f_rtime_self;
+ } PgStat_FunctionEntry;
+
+ /* ----------
   * PgStat_MsgTabstat            Sent by the backend to report table
   *                                and buffer access statistics.
   * ----------
***************
*** 211,216 ****
--- 247,286 ----


  /* ----------
+  * PgStat_MsgFuncstat            Sent by the backend to report function
+  *                                access statistics.
+  * ----------
+  */
+ #define PGSTAT_NUM_FUNCENTRIES  \
+     ((PGSTAT_MSG_PAYLOAD - sizeof(Oid) - 3 * sizeof(int))  \
+      / sizeof(PgStat_FunctionEntry))
+
+ typedef struct PgStat_MsgFuncstat
+ {
+     PgStat_MsgHdr m_hdr;
+     Oid            m_databaseid;
+     int            m_nentries;
+     PgStat_FunctionEntry m_entry[PGSTAT_NUM_FUNCENTRIES];
+ } PgStat_MsgFuncstat;
+
+ /* ----------
+  * PgStat_MsgFuncpurge            Sent by the backend to tell the collector
+  *                                about dead functions.
+  * ----------
+  */
+ #define PGSTAT_NUM_FUNCPURGE  \
+     ((PGSTAT_MSG_PAYLOAD - sizeof(Oid) - sizeof(int))  \
+      / sizeof(Oid))
+
+ typedef struct PgStat_MsgFuncpurge
+ {
+     PgStat_MsgHdr m_hdr;
+     Oid            m_databaseid;
+     int            m_nentries;
+     Oid            m_functionid[PGSTAT_NUM_FUNCPURGE];
+ } PgStat_MsgFuncpurge;
+
+ /* ----------
   * PgStat_MsgDropdb                Sent by the backend to tell the collector
   *                                about a dropped database
   * ----------
***************
*** 309,314 ****
--- 379,386 ----
      PgStat_MsgDummy msg_dummy;
      PgStat_MsgTabstat msg_tabstat;
      PgStat_MsgTabpurge msg_tabpurge;
+     PgStat_MsgFuncstat msg_funcstat;
+     PgStat_MsgFuncpurge msg_funcpurge;
      PgStat_MsgDropdb msg_dropdb;
      PgStat_MsgResetcounter msg_resetcounter;
      PgStat_MsgAutovacStart msg_autovacuum;
***************
*** 347,356 ****
      TimestampTz last_autovac_time;

      /*
!      * tables must be last in the struct, because we don't write the pointer
!      * out to the stats file.
       */
      HTAB       *tables;
  } PgStat_StatDBEntry;


--- 419,429 ----
      TimestampTz last_autovac_time;

      /*
!      * tables and functions must be last in the struct, because we don't
!      * write the pointer out to the stats file.
       */
      HTAB       *tables;
+     HTAB       *functions;
  } PgStat_StatDBEntry;


***************
*** 385,390 ****
--- 458,481 ----
      TimestampTz autovac_analyze_timestamp;        /* autovacuum initiated */
  } PgStat_StatTabEntry;

+ /* ----------
+  * PgStat_StatFuncEntry            The collector's data per function
+  * ----------
+  */
+ typedef struct PgStat_StatFuncEntry
+ {
+     Oid            functionid;
+
+     PgStat_Counter numcalls;
+
+     PgStat_Counter utime;
+     PgStat_Counter stime;
+     PgStat_Counter rtime;
+     PgStat_Counter utime_self;
+     PgStat_Counter stime_self;
+     PgStat_Counter rtime_self;
+ } PgStat_StatFuncEntry ;
+

  /*
   * Global statistics kept in the stats collector
***************
*** 451,456 ****
--- 542,568 ----
      char        st_activity[PGBE_ACTIVITY_SIZE];
  } PgBackendStatus;

+ /*
+  * Function call cpu and time usage.
+  */
+ typedef struct PgStat_FunctionCallUsage
+ {
+     Oid                fn_oid;
+     struct rusage    r_start;
+     struct timeval    t_start;
+     uint64            rtime_us;
+     uint64            utime_us;
+     uint64            stime_us;
+     uint64            self_stime;
+     uint64            self_utime;
+     uint64            self_rtime;
+     PgStat_FunctionEntry *fs;
+     PgStat_FunctionEntry save_fs;
+     uint64            save_stime;
+     uint64            save_utime;
+     uint64            save_rtime;
+ } PgStat_FunctionCallUsage;
+

  /* ----------
   * GUC parameters
***************
*** 458,463 ****
--- 570,577 ----
   */
  extern bool pgstat_track_activities;
  extern bool pgstat_track_counts;
+ extern int pgstat_track_functions;
+

  /*
   * BgWriter statistics counters are updated directly by bgwriter and bufmgr
***************
*** 476,481 ****
--- 590,601 ----
  extern void pgstat_reset_all(void);
  extern void allow_immediate_pgstat_restart(void);

+ /*
+  * function call stats
+  */
+ extern void pgstat_init_function_usage(FunctionCallInfoData *fcinfo, PgStat_FunctionCallUsage *fcu);
+ extern void pgstat_update_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize);
+
  #ifdef EXEC_BACKEND
  extern void PgstatCollectorMain(int argc, char *argv[]);
  #endif
***************
*** 487,494 ****
   */
  extern void pgstat_ping(void);

! extern void pgstat_report_tabstat(bool force);
! extern void pgstat_vacuum_tabstat(void);
  extern void pgstat_drop_database(Oid databaseid);

  extern void pgstat_clear_snapshot(void);
--- 607,614 ----
   */
  extern void pgstat_ping(void);

! extern void pgstat_report_stat(bool force);
! extern void pgstat_vacuum_stat(void);
  extern void pgstat_drop_database(Oid databaseid);

  extern void pgstat_clear_snapshot(void);
***************
*** 547,552 ****
--- 667,698 ----
          if (pgstat_track_counts && (rel)->pgstat_info != NULL)        \
              (rel)->pgstat_info->t_counts.t_blocks_hit++;            \
      } while (0)
+ #define pgstat_count_function_calls(s)                                        \
+     do {                                                                \
+         if (pgstat_track_functions && s != NULL)            \
+             s->f_numcalls++; \
+     } while (0)
+ #define pgstat_count_function_rtime(s, n, self)                                        \
+     do {                                                                \
+         if (pgstat_track_functions && s != NULL) {            \
+             s->f_rtime += (n); \
+             s->f_rtime_self += (self); \
+                 } \
+     } while (0)
+ #define pgstat_count_function_utime(s, n, self)                                        \
+     do {                                                                \
+         if (pgstat_track_functions && s != NULL) {            \
+             s->f_utime += (n); \
+             s->f_utime_self += (self); \
+                 } \
+     } while (0)
+ #define pgstat_count_function_stime(s, n, self)                                        \
+     do {                                                                \
+         if (pgstat_track_functions && s != NULL) {            \
+             s->f_stime += (n); \
+             s->f_stime_self += (self); \
+                 } \
+     } while (0)

  extern void pgstat_count_heap_insert(Relation rel);
  extern void pgstat_count_heap_update(Relation rel, bool hot);
***************
*** 574,579 ****
--- 720,726 ----
  extern PgStat_StatDBEntry *pgstat_fetch_stat_dbentry(Oid dbid);
  extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
  extern PgBackendStatus *pgstat_fetch_stat_beentry(int beid);
+ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
  extern int    pgstat_fetch_stat_numbackends(void);
  extern PgStat_GlobalStats *pgstat_fetch_global(void);

*** ./src/test/regress/expected/rules.out.orig    2008-03-20 19:46:37.000000000 +0200
--- ./src/test/regress/expected/rules.out    2008-03-22 16:38:25.000000000 +0200
***************
*** 1296,1301 ****
--- 1296,1302 ----
   pg_stat_database         | SELECT d.oid AS datid, d.datname, pg_stat_get_db_numbackends(d.oid) AS numbackends,
pg_stat_get_db_xact_commit(d.oid)AS xact_commit, pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
(pg_stat_get_db_blocks_fetched(d.oid)- pg_stat_get_db_blocks_hit(d.oid)) AS blks_read, pg_stat_get_db_blocks_hit(d.oid)
ASblks_hit, pg_stat_get_db_tuples_returned(d.oid) AS tup_returned, pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
pg_stat_get_db_tuples_inserted(d.oid)AS tup_inserted, pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
pg_stat_get_db_tuples_deleted(d.oid)AS tup_deleted FROM pg_database d; 
   pg_stat_sys_indexes      | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid,
pg_stat_all_indexes.schemaname,pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname,
pg_stat_all_indexes.idx_scan,pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM
pg_stat_all_indexesWHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name,
'information_schema'::name]))OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text)); 
   pg_stat_sys_tables       | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname,
pg_stat_all_tables.relname,pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan,
pg_stat_all_tables.idx_tup_fetch,pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd,
pg_stat_all_tables.n_tup_del,pg_stat_all_tables.n_tup_hot_upd, pg_stat_all_tables.n_live_tup,
pg_stat_all_tables.n_dead_tup,pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum,
pg_stat_all_tables.last_analyze,pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE
((pg_stat_all_tables.schemaname= ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR
(pg_stat_all_tables.schemaname~ '^pg_toast'::text)); 
+  pg_stat_user_functions   | SELECT p.oid AS funcid, n.nspname AS schemaname, p.proname AS funcname,
pg_stat_get_function_calls(p.oid)AS calls, (pg_stat_get_function_rtime(p.oid) / 1000) AS total_time,
((pg_stat_get_function_utime(p.oid)+ pg_stat_get_function_stime(p.oid)) / 1000) AS total_cpu,
(pg_stat_get_function_self_rtime(p.oid)/ 1000) AS self_time, ((pg_stat_get_function_self_utime(p.oid) +
pg_stat_get_function_self_stime(p.oid))/ 1000) AS self_cpu FROM (pg_proc p LEFT JOIN pg_namespace n ON ((n.oid =
p.pronamespace)))WHERE (pg_stat_get_function_calls(p.oid) IS NOT NULL); 
   pg_stat_user_indexes     | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid,
pg_stat_all_indexes.schemaname,pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname,
pg_stat_all_indexes.idx_scan,pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM
pg_stat_all_indexesWHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name,
'information_schema'::name]))AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text)); 
   pg_stat_user_tables      | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname,
pg_stat_all_tables.relname,pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan,
pg_stat_all_tables.idx_tup_fetch,pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd,
pg_stat_all_tables.n_tup_del,pg_stat_all_tables.n_tup_hot_upd, pg_stat_all_tables.n_live_tup,
pg_stat_all_tables.n_dead_tup,pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum,
pg_stat_all_tables.last_analyze,pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE
((pg_stat_all_tables.schemaname<> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND
(pg_stat_all_tables.schemaname!~ '^pg_toast'::text)); 
   pg_statio_all_indexes    | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname
ASindexrelname, (pg_stat_get_blocks_fetched(i.oid) - pg_stat_get_blocks_hit(i.oid)) AS idx_blks_read,
pg_stat_get_blocks_hit(i.oid)AS idx_blks_hit FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN
pg_classi ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY
(ARRAY['r'::"char",'t'::"char"])); 
***************
*** 1327,1333 ****
   shoelace_obsolete        | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit,shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE
(shoe.slcolor= shoelace.sl_color)))); 
   street                   | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ##
r.thepath);
   toyemp                   | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (49 rows)

  SELECT tablename, rulename, definition FROM pg_rules
      ORDER BY tablename, rulename;
--- 1328,1334 ----
   shoelace_obsolete        | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit,shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE
(shoe.slcolor= shoelace.sl_color)))); 
   street                   | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ##
r.thepath);
   toyemp                   | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (50 rows)

  SELECT tablename, rulename, definition FROM pg_rules
      ORDER BY tablename, rulename;

pgsql-patches by date:

Previous
From: Zoltan Boszormenyi
Date:
Subject: Re: Re: int8/float8/time/timestamp[tz]/float4 passed by value, was Re: Fix HAVE_LONG[_LONG]_INT_64 to really define to 1
Next
From: Zdenek Kotala
Date:
Subject: Re: actualised execute using patch