stored procedure stats in collector - Mailing list pgsql-patches

From Martin Pihlak
Subject stored procedure stats in collector
Date
Msg-id 47E69BD0.6070403@gmail.com
Whole thread Raw
Responses Re: stored procedure stats in collector  (Volkan YAZICI <yazicivo@ttmail.com>)
List pgsql-patches
Attached is a patch that enables tracking function calls through
the stats subsystem. Original discussion:
http://archives.postgresql.org/pgsql-hackers/2007-07/msg00377.php

Introduces new guc variable - track_functions. Possible values are:
none - no collection, default
pl - tracks procedural language functions
all - tracks procedural, SQL and C (not internal) functions

The results are visible from pg_stat_get_function_* functions and
pg_stat_user_functions view.


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-22 19:38:04.000000000 +0200
***************
*** 27,32 ****
--- 27,33 ----
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
+ #include "pgstat.h"


  /*
***************
*** 189,194 ****
--- 190,196 ----
      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)
      {
***************
*** 215,220 ****
--- 217,236 ----
      finfo->fn_strict = procedureStruct->proisstrict;
      finfo->fn_retset = procedureStruct->proretset;

+     /* check if we need stats for the function */
+     switch (procedureStruct->prolang) {
+         case INTERNALlanguageId:
+             finfo->fn_stats = false;
+             break;
+         case ClanguageId:
+         case SQLlanguageId:
+             finfo->fn_stats = (pgstat_track_functions == TRACK_FUNC_ALL);
+             break;
+         default:
+             finfo->fn_stats = (pgstat_track_functions != TRACK_FUNC_OFF);
+             break;
+     }
+
      /*
       * If it has prosecdef set, or non-null proconfig, use
       * fmgr_security_definer call handler.
*** ./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: "Guillaume Smet"
Date:
Subject: Re: Logging conflicted queries on deadlocks
Next
From: Volkan YAZICI
Date:
Subject: Re: stored procedure stats in collector