diff -Nru mysql-5.0.67/include/mysql_com.h mysql-5.0.67.userstats/include/mysql_com.h
--- mysql-5.0.67/include/mysql_com.h	Mon Aug  4 15:19:12 2008
+++ mysql-5.0.67.userstats/include/mysql_com.h	Wed Sep  3 11:58:41 2008
@@ -106,6 +106,8 @@
 					   thread */
 #define REFRESH_MASTER          128     /* Remove all bin logs in the index
 					   and truncate the index */
+#define REFRESH_TABLE_STATS     256     /* Refresh table stats hash table */
+#define REFRESH_INDEX_STATS     512     /* Refresh index stats hash table */
 
 /* The following can't be set with mysql_refresh() */
 #define REFRESH_READ_LOCK	16384	/* Lock tables for read */
diff -Nru mysql-5.0.67/sql/ha_innodb.cc mysql-5.0.67.userstats/sql/ha_innodb.cc
--- mysql-5.0.67/sql/ha_innodb.cc	Mon Aug  4 15:20:03 2008
+++ mysql-5.0.67.userstats/sql/ha_innodb.cc	Wed Sep  3 11:58:41 2008
@@ -3286,6 +3286,8 @@
 
 	error = row_insert_for_mysql((byte*) record, prebuilt);
 
+        if (error == DB_SUCCESS) rows_changed++;
+
 	if (error == DB_SUCCESS && auto_inc_used) {
 
         	/* Fetch the value that was set in the autoincrement field */
@@ -3558,6 +3560,8 @@
 		}
 	}
 
+	if (error == DB_SUCCESS) rows_changed++;
+
 	innodb_srv_conc_exit_innodb(prebuilt->trx);
 
 	error = convert_error_code_to_mysql(error, user_thd);
@@ -3606,6 +3610,8 @@
 
 	error = row_update_for_mysql((byte*) record, prebuilt);
 
+	if (error == DB_SUCCESS) rows_changed++;
+
 	innodb_srv_conc_exit_innodb(prebuilt->trx);
 
 	error = convert_error_code_to_mysql(error, user_thd);
@@ -3885,6 +3891,9 @@
 	if (ret == DB_SUCCESS) {
 		error = 0;
 		table->status = 0;
+                rows_read++;
+                if (active_index >= 0 && active_index < MAX_KEY)
+                        index_rows_read[active_index]++;
 
 	} else if (ret == DB_RECORD_NOT_FOUND) {
 		error = HA_ERR_KEY_NOT_FOUND;
@@ -4038,6 +4047,9 @@
 	if (ret == DB_SUCCESS) {
 		error = 0;
 		table->status = 0;
+                rows_read++;
+                if (active_index >= 0 && active_index < MAX_KEY)
+                        index_rows_read[active_index]++;
 
 	} else if (ret == DB_RECORD_NOT_FOUND) {
 		error = HA_ERR_END_OF_FILE;
diff -Nru mysql-5.0.67/sql/ha_myisam.cc mysql-5.0.67.userstats/sql/ha_myisam.cc
--- mysql-5.0.67/sql/ha_myisam.cc	Mon Aug  4 15:20:03 2008
+++ mysql-5.0.67.userstats/sql/ha_myisam.cc	Wed Sep  3 11:58:41 2008
@@ -670,7 +670,9 @@
     if ((error= update_auto_increment()))
       return error;
   }
-  return mi_write(file,buf);
+  int error=mi_write(file,buf);
+  if (!error) rows_changed++;
+  return error;
 }
 
 int ha_myisam::check(THD* thd, HA_CHECK_OPT* check_opt)
@@ -1518,13 +1520,17 @@
   statistic_increment(table->in_use->status_var.ha_update_count,&LOCK_status);
   if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE)
     table->timestamp_field->set_time();
-  return mi_update(file,old_data,new_data);
+  int error=mi_update(file,old_data,new_data);
+  if (!error) rows_changed++;
+  return error;
 }
 
 int ha_myisam::delete_row(const byte * buf)
 {
   statistic_increment(table->in_use->status_var.ha_delete_count,&LOCK_status);
-  return mi_delete(file,buf);
+  int error=mi_delete(file,buf);
+  if (!error) rows_changed++;
+  return error;
 }
 
 int ha_myisam::index_read(byte * buf, const byte * key,
@@ -1535,6 +1541,13 @@
 		      &LOCK_status);
   int error=mi_rkey(file,buf,active_index, key, key_len, find_flag);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1545,6 +1558,14 @@
 		      &LOCK_status);
   int error=mi_rkey(file,buf,index, key, key_len, find_flag);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+//    int inx = (active_index == -1) ? file->lastinx : active_index;
+    int inx = index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1555,6 +1576,13 @@
 		      &LOCK_status);
   int error=mi_rkey(file,buf,active_index, key, key_len, HA_READ_PREFIX_LAST);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1565,6 +1593,13 @@
 		      &LOCK_status);
   int error=mi_rnext(file,buf,active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1575,6 +1610,13 @@
 		      &LOCK_status);
   int error=mi_rprev(file,buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1585,6 +1627,13 @@
 		      &LOCK_status);
   int error=mi_rfirst(file, buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1595,6 +1644,13 @@
 		      &LOCK_status);
   int error=mi_rlast(file, buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1611,6 +1667,13 @@
     error= mi_rnext_same(file,buf);
   } while (error == HA_ERR_RECORD_DELETED);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) {
+    rows_read++;
+
+    int inx = (active_index == -1) ? file->lastinx : active_index;
+    if (inx >= 0 && inx < MAX_KEY)
+      index_rows_read[inx]++;
+  }
   return error;
 }
 
@@ -1628,6 +1691,7 @@
 		      &LOCK_status);
   int error=mi_scan(file, buf);
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) rows_read++;
   return error;
 }
 
@@ -1642,6 +1706,7 @@
 		      &LOCK_status);
   int error=mi_rrnd(file, buf, my_get_ptr(pos,ref_length));
   table->status=error ? STATUS_NOT_FOUND: 0;
+  if (!error) rows_read++;
   return error;
 }
 
diff -Nru mysql-5.0.67/sql/handler.cc mysql-5.0.67.userstats/sql/handler.cc
--- mysql-5.0.67/sql/handler.cc	Mon Aug  4 15:20:04 2008
+++ mysql-5.0.67.userstats/sql/handler.cc	Wed Sep  3 11:58:41 2008
@@ -20,6 +20,12 @@
 #pragma implementation				// gcc: Class implementation
 #endif
 
+/* 
+ * Ugh. Something is fishy with the SAFE_MUTEX stuff in include/my_pthread.h.
+ * This makes things compile with gcc 4.1
+ */
+#include <string>
+
 #include "mysql_priv.h"
 #include "ha_heap.h"
 #include "ha_myisam.h"
@@ -756,6 +762,7 @@
         error=1;
       }
       statistic_increment(thd->status_var.ha_commit_count,&LOCK_status);
+      thd->diff_commit_trans++;
       *ht= 0;
     }
     trans->nht=0;
@@ -812,6 +819,7 @@
         error=1;
       }
       statistic_increment(thd->status_var.ha_rollback_count,&LOCK_status);
+      thd->diff_rollback_trans++;
       *ht= 0;
     }
     trans->nht=0;
@@ -1205,6 +1213,7 @@
       error=1;
     }
     statistic_increment(thd->status_var.ha_rollback_count,&LOCK_status);
+    thd->diff_rollback_trans++;
     *ht=0; // keep it conveniently zero-filled
   }
   DBUG_RETURN(error);
@@ -1437,6 +1446,8 @@
     else
       dupp_ref=ref+ALIGN_SIZE(ref_length);
   }
+  rows_read = rows_changed = 0;
+  memset(index_rows_read, 0, sizeof(index_rows_read));
   DBUG_RETURN(error);
 }
 
@@ -2222,6 +2233,97 @@
   return error;
 }
 
+// Updates the global table stats with the TABLE this handler represents.
+void handler::update_global_table_stats() {
+  if (!rows_read && !rows_changed) return;  // Nothing to update.
+  // table_cache_key is db_name + '\0' + table_name + '\0'.
+  if (!table->s || !table->s->table_cache_key || !table->s->table_name) return;
+
+  TABLE_STATS* table_stats;
+  char key[NAME_LEN * 2 + 2];
+  // [db] + '.' + [table]
+  sprintf(key, "%s.%s", table->s->table_cache_key, table->s->table_name);
+
+  pthread_mutex_lock(&LOCK_global_table_stats);
+  // Gets the global table stats, creating one if necessary.
+  if (!(table_stats = (TABLE_STATS*)hash_search(&global_table_stats,
+                                                (byte*)key,
+                                                strlen(key)))) {
+    if (!(table_stats = ((TABLE_STATS*)
+                         my_malloc(sizeof(TABLE_STATS), MYF(MY_WME))))) {
+      // Out of memory.
+      sql_print_error("Allocating table stats failed.");
+      goto end;
+    }
+    strncpy(table_stats->table, key, sizeof(table_stats->table));
+    table_stats->rows_read = 0;
+    table_stats->rows_changed = 0;
+    table_stats->rows_changed_x_indexes = 0;
+
+    if (my_hash_insert(&global_table_stats, (byte*)table_stats)) {
+      // Out of memory.
+      sql_print_error("Inserting table stats failed.");
+      my_free((char*)table_stats, 0);
+      goto end;
+    }
+  }
+  // Updates the global table stats.
+  table_stats->rows_read += rows_read;
+  table_stats->rows_changed += rows_changed;
+  table_stats->rows_changed_x_indexes +=
+      rows_changed * (table->s->keys ? table->s->keys : 1);
+  rows_read = rows_changed = 0;
+end:
+  pthread_mutex_unlock(&LOCK_global_table_stats);
+}
+
+// Updates the global index stats with this handler's accumulated index reads.
+void handler::update_global_index_stats() {
+  // table_cache_key is db_name + '\0' + table_name + '\0'.
+  if (!table->s || !table->s->table_cache_key || !table->s->table_name) return;
+
+  for (int x = 0; x < table->s->keys; x++) {
+    if (index_rows_read[x]) {
+      // Rows were read using this index.
+      KEY* key_info = &table->key_info[x];
+
+      if (!key_info->name) continue;
+
+      INDEX_STATS* index_stats;
+      char key[NAME_LEN * 3 + 3];
+      // [db] + '.' + [table] + '.' + [index]
+      sprintf(key, "%s.%s.%s",  table->s->table_cache_key,
+              table->s->table_name, key_info->name);
+
+      pthread_mutex_lock(&LOCK_global_index_stats);
+      // Gets the global index stats, creating one if necessary.
+      if (!(index_stats = (INDEX_STATS*)hash_search(&global_index_stats,
+                                                    (byte*)key,
+                                                    strlen(key)))) {
+        if (!(index_stats = ((INDEX_STATS*)
+                             my_malloc(sizeof(INDEX_STATS), MYF(MY_WME))))) {
+          // Out of memory.
+          sql_print_error("Allocating index stats failed.");
+          goto end;
+        }
+        strncpy(index_stats->index, key, sizeof(index_stats->index));
+        index_stats->rows_read = 0;
+
+        if (my_hash_insert(&global_index_stats, (byte*)index_stats)) {
+          // Out of memory.
+          sql_print_error("Inserting index stats failed.");
+          my_free((char*)index_stats, 0);
+          goto end;
+        }
+      }
+      // Updates the global index stats.
+      index_stats->rows_read += index_rows_read[x];
+      index_rows_read[x] = 0;
+end:
+      pthread_mutex_unlock(&LOCK_global_index_stats);
+    }
+  }
+}
 
 /****************************************************************************
 ** Some general functions that isn't in the handler class
diff -Nru mysql-5.0.67/sql/handler.h mysql-5.0.67.userstats/sql/handler.h
--- mysql-5.0.67/sql/handler.h	Mon Aug  4 15:20:04 2008
+++ mysql-5.0.67.userstats/sql/handler.h	Wed Sep  3 11:58:41 2008
@@ -32,6 +32,10 @@
 #define USING_TRANSACTIONS
 #endif
 
+#if MAX_KEY > 128
+#error MAX_KEY is too large.  Values up to 128 are supported.
+#endif
+
 // the following is for checking tables
 
 #define HA_ADMIN_ALREADY_DONE	  1
@@ -604,6 +608,9 @@
   bool  auto_increment_column_changed;
   bool implicit_emptied;                /* Can be !=0 only if HEAP */
   const COND *pushed_cond;
+  ulonglong rows_read;
+  ulonglong rows_changed;
+  ulonglong index_rows_read[MAX_KEY];
 
   handler(const handlerton *ht_arg, TABLE *table_arg) :table(table_arg),
     ht(ht_arg),
@@ -615,8 +622,10 @@
     ref_length(sizeof(my_off_t)), block_size(0),
     raid_type(0), ft_handler(0), inited(NONE),
     locked(FALSE), implicit_emptied(0),
-    pushed_cond(NULL)
-    {}
+    pushed_cond(NULL), rows_read(0), rows_changed(0)
+    {
+      memset(index_rows_read, 0, sizeof(index_rows_read));
+    }
   virtual ~handler(void) { DBUG_ASSERT(locked == FALSE); /* TODO: DBUG_ASSERT(inited == NONE); */ }
   virtual handler *clone(MEM_ROOT *mem_root);
   int ha_open(const char *name, int mode, int test_if_locked);
@@ -625,7 +634,11 @@
   virtual void print_error(int error, myf errflag);
   virtual bool get_error_message(int error, String *buf);
   uint get_dup_key(int error);
-  void change_table_ptr(TABLE *table_arg) { table=table_arg; }
+  void change_table_ptr(TABLE *table_arg) {
+    table=table_arg;
+    rows_read = rows_changed = 0;
+    memset(index_rows_read, 0, sizeof(index_rows_read));
+  }
   virtual double scan_time()
     { return ulonglong2double(data_file_length) / IO_SIZE + 2; }
   virtual double read_time(uint index, uint ranges, ha_rows rows)
@@ -885,6 +898,9 @@
   virtual bool is_crashed() const  { return 0; }
   virtual bool auto_repair() const { return 0; }
 
+  void update_global_table_stats();
+  void update_global_index_stats();
+
   /*
     default rename_table() and delete_table() rename/delete files with a
     given name and extensions from bas_ext()
diff -Nru mysql-5.0.67/sql/lex.h mysql-5.0.67.userstats/sql/lex.h
--- mysql-5.0.67/sql/lex.h	Mon Aug  4 15:20:07 2008
+++ mysql-5.0.67.userstats/sql/lex.h	Wed Sep  3 11:58:41 2008
@@ -238,6 +238,7 @@
   { "IN",		SYM(IN_SYM)},
   { "INDEX",		SYM(INDEX_SYM)},
   { "INDEXES",		SYM(INDEXES)},
+  { "INDEX_STATISTICS",	SYM(INDEX_STATS_SYM)},
   { "INFILE",		SYM(INFILE)},
   { "INNER",		SYM(INNER_SYM)},
   { "INNOBASE",		SYM(INNOBASE_SYM)},
@@ -487,6 +488,7 @@
   { "TABLE",		SYM(TABLE_SYM)},
   { "TABLES",		SYM(TABLES)},
   { "TABLESPACE",	SYM(TABLESPACE)},
+  { "TABLE_STATISTICS",	SYM(TABLE_STATS_SYM)},
   { "TEMPORARY",	SYM(TEMPORARY)},
   { "TEMPTABLE",	SYM(TEMPTABLE_SYM)},
   { "TERMINATED",	SYM(TERMINATED)},
@@ -524,6 +526,7 @@
   { "USE",		SYM(USE_SYM)},
   { "USER",		SYM(USER)},
   { "USER_RESOURCES",	SYM(RESOURCES)},
+  { "USER_STATISTICS",	SYM(USER_STATS_SYM)},
   { "USE_FRM",		SYM(USE_FRM)},
   { "USING",		SYM(USING)},
   { "UTC_DATE",         SYM(UTC_DATE_SYM)},
diff -Nru mysql-5.0.67/sql/mysql_priv.h mysql-5.0.67.userstats/sql/mysql_priv.h
--- mysql-5.0.67/sql/mysql_priv.h	Mon Aug  4 15:20:07 2008
+++ mysql-5.0.67.userstats/sql/mysql_priv.h	Wed Sep  3 11:58:41 2008
@@ -744,7 +744,13 @@
 bool multi_delete_set_locks_and_link_aux_tables(LEX *lex);
 void init_max_user_conn(void);
 void init_update_queries(void);
+void init_global_user_stats(void);
+void init_global_table_stats(void);
+void init_global_index_stats(void);
 void free_max_user_conn(void);
+void free_global_user_stats(void);
+void free_global_table_stats(void);
+void free_global_index_stats(void);
 pthread_handler_t handle_one_connection(void *arg);
 pthread_handler_t handle_bootstrap(void *arg);
 void end_thread(THD *thd,bool put_in_cache);
@@ -965,6 +971,9 @@
 void mysqld_list_processes(THD *thd,const char *user,bool verbose);
 int mysqld_show_status(THD *thd);
 int mysqld_show_variables(THD *thd,const char *wild);
+int mysqld_show_user_stats(THD *thd, const char *wild);
+int mysqld_show_table_stats(THD *thd, const char *wild);
+int mysqld_show_index_stats(THD *thd, const char *wild);
 bool mysqld_show_storage_engines(THD *thd);
 bool mysqld_show_privileges(THD *thd);
 bool mysqld_show_column_types(THD *thd);
@@ -1395,6 +1404,12 @@
 extern struct system_variables max_system_variables;
 extern struct system_status_var global_status_var;
 extern struct rand_struct sql_rand;
+extern HASH global_user_stats;
+extern pthread_mutex_t LOCK_global_user_stats;
+extern HASH global_table_stats;
+extern pthread_mutex_t LOCK_global_table_stats;
+extern HASH global_index_stats;
+extern pthread_mutex_t LOCK_global_index_stats;
 
 extern const char *opt_date_time_formats[];
 extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[];
diff -Nru mysql-5.0.67/sql/mysqld.cc mysql-5.0.67.userstats/sql/mysqld.cc
--- mysql-5.0.67/sql/mysqld.cc	Mon Aug  4 15:20:07 2008
+++ mysql-5.0.67.userstats/sql/mysqld.cc	Wed Sep  3 11:58:41 2008
@@ -547,6 +547,9 @@
 		LOCK_crypt, LOCK_bytes_sent, LOCK_bytes_received,
 	        LOCK_global_system_variables,
 		LOCK_user_conn, LOCK_slave_list, LOCK_active_mi;
+pthread_mutex_t LOCK_global_user_stats;
+pthread_mutex_t LOCK_global_table_stats;
+pthread_mutex_t LOCK_global_index_stats;
 /*
   The below lock protects access to two global server variables:
   max_prepared_stmt_count and prepared_stmt_count. These variables
@@ -1188,6 +1191,9 @@
   x_free(opt_secure_file_priv);
   bitmap_free(&temp_pool);
   free_max_user_conn();
+  free_global_user_stats();
+  free_global_table_stats();
+  free_global_index_stats();
 #ifdef HAVE_REPLICATION
   end_slave_list();
   free_list(&replicate_do_db);
@@ -1302,6 +1308,9 @@
   (void) pthread_cond_destroy(&COND_thread_cache);
   (void) pthread_cond_destroy(&COND_flush_thread_cache);
   (void) pthread_cond_destroy(&COND_manager);
+  (void) pthread_mutex_destroy(&LOCK_global_user_stats);
+  (void) pthread_mutex_destroy(&LOCK_global_table_stats);
+  (void) pthread_mutex_destroy(&LOCK_global_index_stats);
 }
 
 #endif /*EMBEDDED_LIBRARY*/
@@ -3147,6 +3156,9 @@
   (void) pthread_mutex_init(&LOCK_rpl_status, MY_MUTEX_INIT_FAST);
   (void) pthread_cond_init(&COND_rpl_status, NULL);
 #endif
+  (void) pthread_mutex_init(&LOCK_global_user_stats, MY_MUTEX_INIT_FAST);
+  (void) pthread_mutex_init(&LOCK_global_table_stats, MY_MUTEX_INIT_FAST);
+  (void) pthread_mutex_init(&LOCK_global_index_stats, MY_MUTEX_INIT_FAST);
   sp_cache_init();
   /* Parameter for threads created for connections */
   (void) pthread_attr_init(&connection_attrib);
@@ -3418,6 +3430,10 @@
     sql_print_error("Out of memory");
     unireg_abort(1);
   }
+
+  init_global_table_stats();
+  init_global_index_stats();
+
   if (ha_init())
   {
     sql_print_error("Can't init databases");
@@ -3500,6 +3516,7 @@
 
   init_max_user_conn();
   init_update_queries();
+  init_global_user_stats();
   DBUG_RETURN(0);
 }
 
diff -Nru mysql-5.0.67/sql/sql_base.cc mysql-5.0.67.userstats/sql/sql_base.cc
--- mysql-5.0.67/sql/sql_base.cc	Mon Aug  4 15:20:09 2008
+++ mysql-5.0.67.userstats/sql/sql_base.cc	Wed Sep  3 11:58:41 2008
@@ -625,6 +625,12 @@
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
 
+  if(table->file)
+  {
+    table->file->update_global_table_stats();
+    table->file->update_global_index_stats();
+  }
+
   *table_ptr=table->next;
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
@@ -670,6 +676,9 @@
 {
   DBUG_ENTER("close_temporary");
   char path[FN_REFLEN];
+
+  table->file->update_global_table_stats();
+  table->file->update_global_index_stats();
   db_type table_type=table->s->db_type;
   strmov(path,table->s->path);
   free_io_cache(table);
diff -Nru mysql-5.0.67/sql/sql_class.cc mysql-5.0.67.userstats/sql/sql_class.cc
--- mysql-5.0.67/sql/sql_class.cc	Mon Aug  4 15:20:09 2008
+++ mysql-5.0.67.userstats/sql/sql_class.cc	Wed Sep  3 11:58:41 2008
@@ -235,6 +235,8 @@
   bzero(ha_data, sizeof(ha_data));
   mysys_var=0;
   binlog_evt_union.do_union= FALSE;
+  busy_time = 0;
+  updated_row_count = 0;
 #ifndef DBUG_OFF
   dbug_sentry=THD_SENTRY_MAGIC;
 #endif
@@ -368,8 +370,55 @@
   total_warn_count= 0;
   update_charset();
   bzero((char *) &status_var, sizeof(status_var));
+  reset_stats();
 }
 
+// Resets stats in a THD.
+void THD::reset_stats(void) {
+  current_connect_time = time(NULL);
+  last_global_update_time = current_connect_time;
+  reset_diff_stats();
+}
+
+// Resets the 'diff' stats, which are used to update global stats.
+void THD::reset_diff_stats(void) {
+  diff_total_busy_time = 0;
+  diff_total_sent_rows = 0;
+  diff_total_updated_rows = 0;
+  diff_select_commands = 0;
+  diff_update_commands = 0;
+  diff_other_commands = 0;
+  diff_commit_trans = 0;
+  diff_rollback_trans = 0;
+}
+
+// Updates 'diff' stats of a THD.
+void THD::update_stats() {
+  diff_total_busy_time += busy_time;
+  diff_total_sent_rows += sent_row_count;
+  diff_total_updated_rows += updated_row_count;
+  // The replication thread has the COM_CONNECT command.
+  if ((old_command == COM_QUERY || command == COM_CONNECT) &&
+      (lex->sql_command >= 0 && lex->sql_command < SQLCOM_END)) {
+    // A SQL query.
+    if (lex->sql_command == SQLCOM_SELECT) {
+      if (lex->orig_sql_command == SQLCOM_END) {
+        diff_select_commands++;
+      } else {
+        // 'SHOW ' commands become SQLCOM_SELECT.
+        diff_other_commands++;
+        // 'SHOW ' commands shouldn't inflate total sent row count.
+        diff_total_sent_rows -= sent_row_count;
+      }
+    } else if (is_update_query(lex->sql_command)) {
+      diff_update_commands++;
+    } else {
+      diff_other_commands++;
+    }
+  }
+  // diff_commit_trans is updated in handler.cc.
+  // diff_rollback_trans is updated in handler.cc.
+}
 
 /*
   Init THD for query processing.
@@ -2384,4 +2433,3 @@
   hash_delete(&xid_cache, (byte *)xid_state);
   pthread_mutex_unlock(&LOCK_xid_cache);
 }
-
diff -Nru mysql-5.0.67/sql/sql_class.h mysql-5.0.67.userstats/sql/sql_class.h
--- mysql-5.0.67/sql/sql_class.h	Mon Aug  4 15:20:09 2008
+++ mysql-5.0.67.userstats/sql/sql_class.h	Wed Sep  3 11:58:41 2008
@@ -1267,6 +1267,8 @@
     first byte of the packet in do_command()
   */
   enum enum_server_command command;
+  // Used to save the command, before it is set to COM_SLEEP.
+  enum enum_server_command old_command;
   uint32     server_id;
   uint32     file_id;			// for LOAD DATA INFILE
   /*
@@ -1380,6 +1382,7 @@
   ulonglong  options;           /* Bitmap of states */
   longlong   row_count_func;    /* For the ROW_COUNT() function */
   ha_rows    cuted_fields;
+  ha_rows    updated_row_count;
 
   /*
     number of rows we actually sent to the client, including "synthetic"
@@ -1548,6 +1551,27 @@
   */
   LOG_INFO*  current_linfo;
   NET*       slave_net;			// network connection from slave -> m.
+
+  /*
+    Used to update global user stats.  The global user stats are updated
+    occasionally with the 'diff' variables.  After the update, the 'diff'
+    variables are reset to 0.
+   */
+  // Time when the current thread connected to MySQL.
+  time_t current_connect_time;
+  // Last time when THD stats were updated in global_user_stats.
+  time_t last_global_update_time;
+  // Busy (non-idle) time for just one command.
+  double busy_time;
+  // Busy time not updated in global_user_stats yet.
+  double diff_total_busy_time;
+  // Number of rows not reflected in global_user_stats yet.
+  ha_rows diff_total_sent_rows, diff_total_updated_rows;
+  // Number of commands not reflected in global_user_stats yet.
+  ulonglong diff_select_commands, diff_update_commands, diff_other_commands;
+  // Number of transactions not reflected in global_user_stats yet.
+  ulonglong diff_commit_trans, diff_rollback_trans;
+
   /* Used by the sys_var class to store temporary values */
   union
   {
@@ -1605,6 +1629,9 @@
     alloc_root.
   */
   void init_for_queries();
+  void reset_stats(void);
+  void reset_diff_stats(void);
+  void update_stats(void);
   void change_user(void);
   void cleanup(void);
   void cleanup_after_query();
diff -Nru mysql-5.0.67/sql/sql_delete.cc mysql-5.0.67.userstats/sql/sql_delete.cc
--- mysql-5.0.67/sql/sql_delete.cc	Mon Aug  4 15:20:09 2008
+++ mysql-5.0.67.userstats/sql/sql_delete.cc	Wed Sep  3 11:58:41 2008
@@ -358,6 +358,7 @@
     send_ok(thd,deleted);
     DBUG_PRINT("info",("%ld records deleted",(long) deleted));
   }
+  thd->updated_row_count += deleted;
   DBUG_RETURN(error >= 0 || thd->net.report_error);
 }
 
@@ -869,6 +870,7 @@
     thd->row_count_func= deleted;
     ::send_ok(thd, deleted);
   }
+  thd->updated_row_count += deleted;
   return 0;
 }
 
diff -Nru mysql-5.0.67/sql/sql_insert.cc mysql-5.0.67.userstats/sql/sql_insert.cc
--- mysql-5.0.67/sql/sql_insert.cc	Mon Aug  4 15:20:09 2008
+++ mysql-5.0.67.userstats/sql/sql_insert.cc	Wed Sep  3 11:58:41 2008
@@ -989,6 +989,7 @@
     thd->row_count_func= info.copied + info.deleted + updated;
     ::send_ok(thd, (ulong) thd->row_count_func, id, buff);
   }
+  thd->updated_row_count += thd->row_count_func;
   thd->abort_on_warning= 0;
   DBUG_RETURN(FALSE);
 
diff -Nru mysql-5.0.67/sql/sql_lex.h mysql-5.0.67.userstats/sql/sql_lex.h
--- mysql-5.0.67/sql/sql_lex.h	Mon Aug  4 15:20:10 2008
+++ mysql-5.0.67.userstats/sql/sql_lex.h	Wed Sep  3 12:05:25 2008
@@ -94,6 +94,7 @@
   SQLCOM_XA_START, SQLCOM_XA_END, SQLCOM_XA_PREPARE,
   SQLCOM_XA_COMMIT, SQLCOM_XA_ROLLBACK, SQLCOM_XA_RECOVER,
   SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE,
+  SQLCOM_SHOW_USER_STATS, SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS,
   SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES,
 
   /*
diff -Nru mysql-5.0.67/sql/sql_parse.cc mysql-5.0.67.userstats/sql/sql_parse.cc
--- mysql-5.0.67/sql/sql_parse.cc	Mon Aug  4 15:20:10 2008
+++ mysql-5.0.67.userstats/sql/sql_parse.cc	Wed Sep  3 11:58:42 2008
@@ -78,6 +78,15 @@
 static bool check_show_create_table_access(THD *thd, TABLE_LIST *table);
 static bool test_if_data_home_dir(const char *dir);
 
+// Set stats for concurrent connections displayed by mysqld_show().
+static void set_concurrent_connections_stats();
+
+// Increments connection count for user.
+static int increment_connection_count(THD* thd, bool use_lock);
+
+// Uses the THD to update the global stats.
+static void update_global_user_stats(THD* thd);
+
 const char *any_db="*any*";	// Special symbol for check_access
 
 const char *command_name[]={
@@ -98,6 +107,15 @@
 static bool do_command(THD *thd);
 #endif // EMBEDDED_LIBRARY
 
+HASH global_user_stats;
+extern pthread_mutex_t LOCK_global_user_stats;
+
+HASH global_table_stats;
+extern pthread_mutex_t LOCK_global_table_stats;
+
+HASH global_index_stats;
+extern pthread_mutex_t LOCK_global_index_stats;
+
 #ifdef __WIN__
 extern void win_install_sigabrt_handler(void);
 #endif
@@ -488,13 +506,81 @@
 void init_max_user_conn(void)
 {
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
-  (void) hash_init(&hash_user_connections,system_charset_info,max_connections,
-		   0,0,
-		   (hash_get_key) get_key_conn, (hash_free_key) free_user,
-		   0);
+  if (hash_init(&hash_user_connections,system_charset_info,max_connections,
+                0,0,
+                (hash_get_key) get_key_conn, (hash_free_key) free_user,
+                0)) {
+    sql_print_error("Initializing hash_user_connections failed.");
+    exit(1);
+  }
 #endif
 }
 
+extern "C" byte *get_key_user_stats(USER_STATS *user_stats, uint *length,
+                                    my_bool not_used __attribute__((unused)))
+{
+  *length = strlen(user_stats->user);
+  return (byte*)user_stats->user;
+}
+
+extern "C" void free_user_stats(USER_STATS* user_stats)
+{
+  my_free((char*)user_stats, MYF(0));
+}
+
+void init_global_user_stats(void)
+{
+  if (hash_init(&global_user_stats, system_charset_info, max_connections,
+                0, 0, (hash_get_key)get_key_user_stats,
+                (hash_free_key)free_user_stats, 0)) {
+    sql_print_error("Initializing global_user_stats failed.");
+    exit(1);
+  }
+}
+
+extern "C" byte *get_key_table_stats(TABLE_STATS *table_stats, uint *length,
+                                     my_bool not_used __attribute__((unused)))
+{
+  *length = strlen(table_stats->table);
+  return (byte*)table_stats->table;
+}
+
+extern "C" void free_table_stats(TABLE_STATS* table_stats)
+{
+  my_free((char*)table_stats, MYF(0));
+}
+
+void init_global_table_stats(void)
+{
+  if (hash_init(&global_table_stats, system_charset_info, max_connections,
+                0, 0, (hash_get_key)get_key_table_stats,
+                (hash_free_key)free_table_stats, 0)) {
+    sql_print_error("Initializing global_table_stats failed.");
+    exit(1);
+  }
+}
+
+extern "C" byte *get_key_index_stats(INDEX_STATS *index_stats, uint *length,
+                                     my_bool not_used __attribute__((unused)))
+{
+  *length = strlen(index_stats->index);
+  return (byte*)index_stats->index;
+}
+
+extern "C" void free_index_stats(INDEX_STATS* index_stats)
+{
+  my_free((char*)index_stats, MYF(0));
+}
+
+void init_global_index_stats(void)
+{
+  if (hash_init(&global_index_stats, system_charset_info, max_connections,
+                0, 0, (hash_get_key)get_key_index_stats,
+                (hash_free_key)free_index_stats, 0)) {
+    sql_print_error("Initializing global_index_stats failed.");
+    exit(1);
+  }
+}
 
 /*
   check if user has already too many connections
@@ -598,6 +684,20 @@
 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
 }
 
+void free_global_user_stats(void)
+{
+  hash_free(&global_user_stats);
+}
+
+void free_global_table_stats(void)
+{
+  hash_free(&global_table_stats);
+}
+
+void free_global_index_stats(void)
+{
+  hash_free(&global_index_stats);
+}
 
 
 /*
@@ -650,6 +750,129 @@
   return uc_update_queries[command] != 0;
 }
 
+// 'mysql_system_user' is used for when the user is not defined for a THD.
+static char mysql_system_user[] = "#mysql_system#";
+
+// Returns 'user' if it's not NULL.  Returns 'mysql_system_user' otherwise.
+static char* get_valid_user_string(char* user) {
+  return user ? user : mysql_system_user;
+}
+
+// Increments the global user stats connection count.  If 'use_lock' is true,
+// 'LOCK_global_user_stats' will be locked/unlocked.  Returns 0 on success,
+// 1 on error.
+static int increment_connection_count(THD* thd, bool use_lock) {
+  char* user_string = get_valid_user_string(thd->main_security_ctx.user);
+
+  USER_STATS* user_stats;
+  int return_value = 0;
+
+  if (use_lock) pthread_mutex_lock(&LOCK_global_user_stats);
+  if (!(user_stats = (USER_STATS*)hash_search(&global_user_stats,
+                                              (byte*)user_string,
+                                              strlen(user_string)))) {
+    // First connection for this user.
+    if (!(user_stats = ((USER_STATS*)
+                        my_malloc(sizeof(USER_STATS), MYF(MY_WME))))) {
+      // Out of memory.
+      return_value = 1;
+      goto end;
+    }
+    strncpy(user_stats->user, user_string, sizeof(user_stats->user));
+    user_stats->total_connections = 0;
+    user_stats->concurrent_connections = 0;
+    user_stats->connected_time = 0;
+    user_stats->busy_time = 0;
+    user_stats->rows_fetched = 0;
+    user_stats->rows_updated = 0;
+    user_stats->select_commands = 0;
+    user_stats->update_commands = 0;
+    user_stats->other_commands = 0;
+    user_stats->commit_trans = 0;
+    user_stats->rollback_trans = 0;
+
+    if (my_hash_insert(&global_user_stats, (byte*)user_stats)) {
+      // Out of memory.
+      my_free((char*)user_stats, 0);
+      return_value = 1;
+      goto end;
+    }
+  }
+  user_stats->total_connections++;
+end:
+  if (use_lock) pthread_mutex_unlock(&LOCK_global_user_stats);
+  return return_value;
+}
+
+// Used to update the global user stats.
+static void update_global_user_stats_with_user(THD* thd,
+                                               USER_STATS* user_stats) {
+  time_t current_time = time(NULL);
+  user_stats->connected_time += current_time - thd->last_global_update_time;
+  thd->last_global_update_time = current_time;
+  user_stats->busy_time += thd->diff_total_busy_time;
+  user_stats->rows_fetched += thd->diff_total_sent_rows;
+  user_stats->rows_updated += thd->diff_total_updated_rows;
+  user_stats->select_commands += thd->diff_select_commands;
+  user_stats->update_commands += thd->diff_update_commands;
+  user_stats->other_commands += thd->diff_other_commands;
+  user_stats->commit_trans += thd->diff_commit_trans;
+  user_stats->rollback_trans += thd->diff_rollback_trans;
+}
+
+// Updates the global stats of a thread/user.
+static void update_global_user_stats(THD* thd) {
+  char* user_string = get_valid_user_string(thd->main_security_ctx.user);
+
+  USER_STATS* user_stats;
+  pthread_mutex_lock(&LOCK_global_user_stats);
+  if ((user_stats = (USER_STATS*)hash_search(&global_user_stats,
+                                             (byte*)user_string,
+                                             strlen(user_string)))) {
+    // Found user.
+    update_global_user_stats_with_user(thd, user_stats);
+    thd->reset_diff_stats();
+  } else {
+    // The user name should exist.
+    increment_connection_count(thd, false);
+  }
+  pthread_mutex_unlock(&LOCK_global_user_stats);
+}
+
+// Determines the concurrent number of connections of current threads.
+static void set_concurrent_connections_stats() {
+  USER_STATS* user_stats;
+
+  pthread_mutex_lock(&LOCK_global_user_stats);
+  pthread_mutex_lock(&LOCK_thread_count);
+
+  // Resets all concurrent connections to 0.
+  for (int i = 0; i < global_user_stats.records; ++i) {
+    user_stats = (USER_STATS*)hash_element(&global_user_stats, i);
+    user_stats->concurrent_connections = 0;
+  }
+
+  I_List_iterator<THD> it(threads);
+  THD* thd;
+  // Iterates through the current threads.
+  while ((thd = it++)) {
+    char* user_string = get_valid_user_string(thd->main_security_ctx.user);
+    if ((user_stats = (USER_STATS*)hash_search(&global_user_stats,
+                                               (byte*)user_string,
+                                               strlen(user_string)))) {
+      // Found user.
+      user_stats->concurrent_connections++;
+      update_global_user_stats_with_user(thd, user_stats);
+      thd->reset_diff_stats();
+    } else {
+      // The user name should exist.
+      increment_connection_count(thd, false);
+    }
+  }
+  pthread_mutex_unlock(&LOCK_thread_count);
+  pthread_mutex_unlock(&LOCK_global_user_stats);
+}
+
 /*
   Reset per-hour user resource limits when it has been more than
   an hour since they were last checked
@@ -1147,6 +1370,14 @@
       statistic_increment(aborted_connects,&LOCK_status);
       goto end_thread;
     }
+
+    thd->reset_stats();
+    // Updates global user connection stats.
+    if (increment_connection_count(thd, true)) {
+      net_send_error(thd, ER_OUTOFMEMORY);  // Out of memory
+      goto end_thread;
+    }
+
 #ifdef __NETWARE__
     netware_reg_user(sctx->ip, sctx->user, "MySQL");
 #endif
@@ -1213,6 +1444,8 @@
 
 end_thread:
     close_connection(thd, 0, 1);
+    thd->update_stats();
+    update_global_user_stats(thd);
     end_thread(thd,1);
     /*
       If end_thread returns, we are either running with --one-thread
@@ -1702,6 +1935,9 @@
   }
 
   thd->command=command;
+  // To increment the corrent command counter for user stats, 'command' must
+  // be saved because it is set to COM_SLEEP at the end of this function.
+  thd->old_command = command;
   /*
     Commands which always take a long time are logged into
     the slow log only if opt_log_slow_admin_statements is set.
@@ -2888,6 +3124,22 @@
   }
 #endif
 
+  case SQLCOM_SHOW_USER_STATS:
+  {
+    set_concurrent_connections_stats();
+    res= mysqld_show_user_stats(thd, (lex->wild ? lex->wild->ptr() : NullS));
+    break;
+  }
+  case SQLCOM_SHOW_TABLE_STATS:
+  {
+    res= mysqld_show_table_stats(thd, (lex->wild ? lex->wild->ptr() : NullS));
+    break;
+  }
+  case SQLCOM_SHOW_INDEX_STATS:
+  {
+    res= mysqld_show_index_stats(thd, (lex->wild ? lex->wild->ptr() : NullS));
+    break;
+  }
   case SQLCOM_BACKUP_TABLE:
   {
     DBUG_ASSERT(first_table == all_tables && first_table != 0);
@@ -5995,6 +6247,8 @@
     thd->total_warn_count=0;			// Warnings for this query
     thd->rand_used= 0;
     thd->sent_row_count= thd->examined_row_count= 0;
+    thd->updated_row_count=0;
+    thd->busy_time=0;
   }
   DBUG_VOID_RETURN;
 }
@@ -6165,6 +6419,16 @@
 
   DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on(););
 
+  int start_time_error = 0;
+  int end_time_error = 0;
+  struct timeval start_time, end_time;
+  double start_usecs = 0;
+  double end_usecs = 0;
+  // Gets the start time, in order to measure how long this command takes.
+  if (!(start_time_error = gettimeofday(&start_time, NULL))) {
+    start_usecs = start_time.tv_sec * 1000000.0 + start_time.tv_usec;
+  }
+
   /*
     Warning.
     The purpose of query_cache_send_result_to_client() is to lookup the
@@ -6260,6 +6524,27 @@
     *found_semicolon= NULL;
   }
 
+  // Gets the end time.
+  if (!(end_time_error = gettimeofday(&end_time, NULL))) {
+    end_usecs = end_time.tv_sec * 1000000.0 + end_time.tv_usec;
+  }
+
+  // Calculates the difference between the end and start times.
+  if (end_usecs >= start_usecs && !start_time_error && !end_time_error) {
+    thd->busy_time = (end_usecs - start_usecs) / 1000000;
+    // In case there are bad values, 2629743 is the #seconds in a month.
+    if (thd->busy_time > 2629743) {
+      thd->busy_time = 0;
+    }
+  } else {
+    // end time went back in time, or gettimeofday() failed.
+    thd->busy_time = 0;
+  }
+
+  // Updates THD stats and the global user stats.
+  thd->update_stats();
+  update_global_user_stats(thd);
+
   DBUG_VOID_RETURN;
 }
 
@@ -6887,6 +7172,7 @@
     tables->lock_type= lock_type;
     tables->updating=  for_update;
   }
+
   DBUG_VOID_RETURN;
 }
 
@@ -7252,8 +7538,22 @@
    pthread_mutex_unlock(&LOCK_active_mi);
  }
 #endif
- if (options & REFRESH_USER_RESOURCES)
-   reset_mqh((LEX_USER *) NULL);
+  if (options & REFRESH_USER_RESOURCES)
+    reset_mqh((LEX_USER *) NULL);
+  if (options & REFRESH_TABLE_STATS)
+  {
+    pthread_mutex_lock(&LOCK_global_table_stats);
+    free_global_table_stats();
+    init_global_table_stats();
+    pthread_mutex_unlock(&LOCK_global_table_stats);
+  }
+  if (options & REFRESH_INDEX_STATS)
+  {
+    pthread_mutex_lock(&LOCK_global_index_stats);
+    free_global_index_stats();
+    init_global_index_stats();
+    pthread_mutex_unlock(&LOCK_global_index_stats);
+  }
  *write_to_binlog= tmp_write_to_binlog;
  return result;
 }
diff -Nru mysql-5.0.67/sql/sql_show.cc mysql-5.0.67.userstats/sql/sql_show.cc
--- mysql-5.0.67/sql/sql_show.cc	Mon Aug  4 15:20:11 2008
+++ mysql-5.0.67.userstats/sql/sql_show.cc	Wed Sep  3 11:58:42 2008
@@ -1811,6 +1811,136 @@
   DBUG_RETURN(FALSE);
 }
 
+// Sends the global user stats back to the client.
+int mysqld_show_user_stats(THD *thd, const char *wild) {
+  Protocol *protocol= thd->protocol;
+  List<Item> field_list;
+
+  DBUG_ENTER("mysqld_show_user_stats");
+  field_list.push_back(new Item_empty_string("User", USERNAME_LENGTH + 1));
+  field_list.push_back(new Item_int("Total_connections", 0));
+  field_list.push_back(new Item_int("Concurrent_connections", 0));
+  field_list.push_back(new Item_int("Connected_time", 0));
+  field_list.push_back(new Item_int("Busy_time", 0));
+  field_list.push_back(new Item_int("Rows_fetched", 0));
+  field_list.push_back(new Item_int("Rows_updated", 0));
+  field_list.push_back(new Item_int("Select_commands", 0));
+  field_list.push_back(new Item_int("Update_commands", 0));
+  field_list.push_back(new Item_int("Other_commands", 0));
+  field_list.push_back(new Item_int("Commit_transactions", 0));
+  field_list.push_back(new Item_int("Rollback_transactions", 0));
+  if (protocol->send_fields(&field_list,
+                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+    DBUG_RETURN(TRUE); /* purecov: inspected */
+
+  // Iterates through all the global stats and sends them to the client.
+  // Pattern matching on the username is supported.
+  pthread_mutex_lock(&LOCK_global_user_stats);
+  for (int i = 0; i < global_user_stats.records; ++i) {
+    USER_STATS *user_stats = (USER_STATS*)hash_element(&global_user_stats, i);
+    if (!(wild && wild[0] &&
+          wild_case_compare(system_charset_info, user_stats->user, wild))) {
+      protocol->prepare_for_resend();
+      protocol->store(user_stats->user, system_charset_info);
+      protocol->store((longlong)user_stats->total_connections);
+      protocol->store((longlong)user_stats->concurrent_connections);
+      protocol->store((longlong)user_stats->connected_time);
+      protocol->store((longlong)user_stats->busy_time);
+      protocol->store((longlong)user_stats->rows_fetched);
+      protocol->store((longlong)user_stats->rows_updated);
+      protocol->store((longlong)user_stats->select_commands);
+      protocol->store((longlong)user_stats->update_commands);
+      protocol->store((longlong)user_stats->other_commands);
+      protocol->store((longlong)user_stats->commit_trans);
+      protocol->store((longlong)user_stats->rollback_trans);
+      if (protocol->write())
+        goto err;                               /* purecov: inspected */
+    }
+  }
+  pthread_mutex_unlock(&LOCK_global_user_stats);
+  send_eof(thd);
+  DBUG_RETURN(FALSE);
+
+ err:
+  pthread_mutex_unlock(&LOCK_global_user_stats);
+  DBUG_RETURN(TRUE);
+}
+
+// Sends the global table stats back to the client.
+int mysqld_show_table_stats(THD *thd, const char *wild) {
+  Protocol *protocol= thd->protocol;
+  List<Item> field_list;
+
+  DBUG_ENTER("mysqld_show_table_stats");
+  field_list.push_back(new Item_empty_string("Table", NAME_LEN * 2 + 2));
+  field_list.push_back(new Item_int("Rows_read", 0));
+  field_list.push_back(new Item_int("Rows_changed", 0));
+  field_list.push_back(new Item_int("Rows_changed_x_#indexes", 0));
+  if (protocol->send_fields(&field_list,
+                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+    DBUG_RETURN(TRUE); /* purecov: inspected */
+
+  // Iterates through all the global table stats and sends them to the client.
+  // Pattern matching on the table name is supported.
+  pthread_mutex_lock(&LOCK_global_table_stats);
+  for (int i = 0; i < global_table_stats.records; ++i) {
+    TABLE_STATS *table_stats =
+      (TABLE_STATS*)hash_element(&global_table_stats, i);
+    if (!(wild && wild[0] &&
+          wild_case_compare(system_charset_info, table_stats->table, wild))) {
+      protocol->prepare_for_resend();
+      protocol->store(table_stats->table, system_charset_info);
+      protocol->store((longlong)table_stats->rows_read);
+      protocol->store((longlong)table_stats->rows_changed);
+      protocol->store((longlong)table_stats->rows_changed_x_indexes);
+      if (protocol->write())
+        goto err;                               /* purecov: inspected */
+    }
+  }
+  pthread_mutex_unlock(&LOCK_global_table_stats);
+  send_eof(thd);
+  DBUG_RETURN(FALSE);
+
+ err:
+  pthread_mutex_unlock(&LOCK_global_table_stats);
+  DBUG_RETURN(TRUE);
+}
+
+// Sends the global index stats back to the client.
+int mysqld_show_index_stats(THD *thd, const char *wild) {
+  Protocol *protocol= thd->protocol;
+  List<Item> field_list;
+
+  DBUG_ENTER("mysqld_show_index_stats");
+  field_list.push_back(new Item_empty_string("Index", NAME_LEN * 3 + 3));
+  field_list.push_back(new Item_int("Rows_read", 0));
+  if (protocol->send_fields(&field_list,
+                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+    DBUG_RETURN(TRUE); /* purecov: inspected */
+
+  // Iterates through all the global index stats and sends them to the client.
+  // Pattern matching on the index name is supported.
+  pthread_mutex_lock(&LOCK_global_index_stats);
+  for (int i = 0; i < global_index_stats.records; ++i) {
+    INDEX_STATS *index_stats =
+      (INDEX_STATS*)hash_element(&global_index_stats, i);
+    if (!(wild && wild[0] &&
+          wild_case_compare(system_charset_info, index_stats->index, wild))) {
+      protocol->prepare_for_resend();
+      protocol->store(index_stats->index, system_charset_info);
+      protocol->store((longlong)index_stats->rows_read);
+      if (protocol->write())
+        goto err;                               /* purecov: inspected */
+    }
+  }
+  pthread_mutex_unlock(&LOCK_global_index_stats);
+  send_eof(thd);
+  DBUG_RETURN(FALSE);
+
+ err:
+  pthread_mutex_unlock(&LOCK_global_index_stats);
+  DBUG_RETURN(TRUE);
+}
 
 /* collect status for all running threads */
 
diff -Nru mysql-5.0.67/sql/sql_update.cc mysql-5.0.67.userstats/sql/sql_update.cc
--- mysql-5.0.67/sql/sql_update.cc	Mon Aug  4 15:20:12 2008
+++ mysql-5.0.67.userstats/sql/sql_update.cc	Wed Sep  3 11:58:42 2008
@@ -601,7 +601,8 @@
       (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated;
     send_ok(thd, (ulong) thd->row_count_func,
 	    thd->insert_id_used ? thd->last_insert_id : 0L,buff);
-    DBUG_PRINT("info",("%ld records updated", (long) updated));
+    thd->updated_row_count += thd->row_count_func;
+    DBUG_PRINT("info",("%d records updated",updated));
   }
   thd->count_cuted_fields= CHECK_FIELD_IGNORE;		/* calc cuted fields */
   thd->abort_on_warning= 0;
@@ -1775,5 +1776,6 @@
     (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated;
   ::send_ok(thd, (ulong) thd->row_count_func,
 	    thd->insert_id_used ? thd->last_insert_id : 0L,buff);
+  thd->updated_row_count += thd->row_count_func;
   return FALSE;
 }
diff -Nru mysql-5.0.67/sql/sql_yacc.yy mysql-5.0.67.userstats/sql/sql_yacc.yy
--- mysql-5.0.67/sql/sql_yacc.yy	Mon Aug  4 15:20:12 2008
+++ mysql-5.0.67.userstats/sql/sql_yacc.yy	Wed Sep  3 11:58:42 2008
@@ -679,6 +679,7 @@
 %token  IMPORT
 %token  INDEXES
 %token  INDEX_SYM
+%token	INDEX_STATS_SYM
 %token  INFILE
 %token  INNER_SYM
 %token  INNOBASE_SYM
@@ -947,6 +948,7 @@
 %token  TABLES
 %token  TABLESPACE
 %token  TABLE_SYM
+%token	TABLE_STATS_SYM
 %token  TEMPORARY
 %token  TEMPTABLE_SYM
 %token  TERMINATED
@@ -989,6 +991,7 @@
 %token  UPGRADE_SYM
 %token  USAGE
 %token  USER
+%token	USER_STATS_SYM
 %token  USE_FRM
 %token  USE_SYM
 %token  USING
@@ -7327,6 +7330,18 @@
           {
 	    Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT;
           }
+        | USER_STATS_SYM wild_and_where
+          {
+	    Lex->sql_command = SQLCOM_SHOW_USER_STATS;
+          }
+        | TABLE_STATS_SYM wild_and_where
+          {
+	    Lex->sql_command = SQLCOM_SHOW_TABLE_STATS;
+          }
+        | INDEX_STATS_SYM wild_and_where
+          {
+	    Lex->sql_command = SQLCOM_SHOW_INDEX_STATS;
+          }
 	| CREATE PROCEDURE sp_name
 	  {
 	    LEX *lex= Lex;
@@ -7522,7 +7537,9 @@
         | SLAVE         { Lex->type|= REFRESH_SLAVE; }
         | MASTER_SYM    { Lex->type|= REFRESH_MASTER; }
 	| DES_KEY_FILE	{ Lex->type|= REFRESH_DES_KEY_FILE; }
- 	| RESOURCES     { Lex->type|= REFRESH_USER_RESOURCES; };
+ 	| RESOURCES     { Lex->type|= REFRESH_USER_RESOURCES; }
+ 	| TABLE_STATS_SYM { Lex->type|= REFRESH_TABLE_STATS; }
+ 	| INDEX_STATS_SYM { Lex->type|= REFRESH_INDEX_STATS; };
 
 opt_table_list:
 	/* empty */  {;}
diff -Nru mysql-5.0.67/sql/structs.h mysql-5.0.67.userstats/sql/structs.h
--- mysql-5.0.67/sql/structs.h	Mon Aug  4 15:20:12 2008
+++ mysql-5.0.67.userstats/sql/structs.h	Wed Sep  3 11:58:42 2008
@@ -273,6 +273,28 @@
   time_t intime;
 } USER_CONN;
 
+typedef struct st_user_stats {
+  char user[USERNAME_LENGTH + 1];
+  uint total_connections;
+  uint concurrent_connections;
+  time_t connected_time;  // in seconds
+  double busy_time;       // in seconds
+  ha_rows rows_fetched, rows_updated;
+  ulonglong select_commands, update_commands, other_commands;
+  ulonglong commit_trans, rollback_trans;
+} USER_STATS;
+
+typedef struct st_table_stats {
+  char table[NAME_LEN * 2 + 2];  // [db] + '.' + [table] + '\0'
+  ulonglong rows_read, rows_changed;
+  ulonglong rows_changed_x_indexes;
+} TABLE_STATS;
+
+typedef struct st_index_stats {
+  char index[NAME_LEN * 3 + 3];  // [db] + '.' + [table] + '.' + [index] + '\0'
+  ulonglong rows_read;
+} INDEX_STATS;
+
 	/* Bits in form->update */
 #define REG_MAKE_DUPP		1	/* Make a copy of record when read */
 #define REG_NEW_RECORD		2	/* Write a new record if not found */
