NFSD_FO_UnlockFS,
        NFSD_Threads,
        NFSD_Pool_Threads,
+       NFSD_Pool_Stats,
        NFSD_Versions,
        NFSD_Ports,
        NFSD_MaxBlkSize,
        .owner          = THIS_MODULE,
 };
 
+extern int nfsd_pool_stats_open(struct inode *inode, struct file *file);
+
+static struct file_operations pool_stats_operations = {
+       .open           = nfsd_pool_stats_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+       .owner          = THIS_MODULE,
+};
+
 /*----------------------------------------------------------------------------*/
 /*
  * payload - write methods
                [NFSD_Fh] = {"filehandle", &transaction_ops, S_IWUSR|S_IRUSR},
                [NFSD_Threads] = {"threads", &transaction_ops, S_IWUSR|S_IRUSR},
                [NFSD_Pool_Threads] = {"pool_threads", &transaction_ops, S_IWUSR|S_IRUSR},
+               [NFSD_Pool_Stats] = {"pool_stats", &pool_stats_operations, S_IRUGO},
                [NFSD_Versions] = {"versions", &transaction_ops, S_IWUSR|S_IRUSR},
                [NFSD_Ports] = {"portlist", &transaction_ops, S_IWUSR|S_IRUGO},
                [NFSD_MaxBlkSize] = {"max_block_size", &transaction_ops, S_IWUSR|S_IRUGO},
 
  */
 typedef int            (*svc_thread_fn)(void *);
 
+/* statistics for svc_pool structures */
+struct svc_pool_stats {
+       unsigned long   packets;
+       unsigned long   sockets_queued;
+       unsigned long   threads_woken;
+       unsigned long   overloads_avoided;
+       unsigned long   threads_timedout;
+};
+
 /*
  *
  * RPC service thread pool.
        unsigned int            sp_nrthreads;   /* # of threads in pool */
        struct list_head        sp_all_threads; /* all server threads */
        int                     sp_nwaking;     /* number of threads woken but not yet active */
+       struct svc_pool_stats   sp_stats;       /* statistics on pool operation */
 } ____cacheline_aligned_in_smp;
 
 /*
                        sa_family_t, void (*shutdown)(struct svc_serv *),
                        svc_thread_fn, struct module *);
 int               svc_set_num_threads(struct svc_serv *, struct svc_pool *, int);
+int               svc_pool_stats_open(struct svc_serv *serv, struct file *file);
 void              svc_destroy(struct svc_serv *);
 int               svc_process(struct svc_rqst *);
 int               svc_register(const struct svc_serv *, const unsigned short,
 
                goto out_unlock;
        }
 
+       pool->sp_stats.packets++;
+
        /* Mark transport as busy. It will remain in this state until
         * the provider calls svc_xprt_received. We update XPT_BUSY
         * atomically because it also guards against trying to enqueue
        if (pool->sp_nwaking >= SVC_MAX_WAKING) {
                /* too many threads are runnable and trying to wake up */
                thread_avail = 0;
+               pool->sp_stats.overloads_avoided++;
        }
 
        if (thread_avail) {
                atomic_add(rqstp->rq_reserved, &xprt->xpt_reserved);
                rqstp->rq_waking = 1;
                pool->sp_nwaking++;
+               pool->sp_stats.threads_woken++;
                BUG_ON(xprt->xpt_pool != pool);
                wake_up(&rqstp->rq_wait);
        } else {
                dprintk("svc: transport %p put into queue\n", xprt);
                list_add_tail(&xprt->xpt_ready, &pool->sp_sockets);
+               pool->sp_stats.sockets_queued++;
                BUG_ON(xprt->xpt_pool != pool);
        }
 
        int                     pages;
        struct xdr_buf          *arg;
        DECLARE_WAITQUEUE(wait, current);
+       long                    time_left;
 
        dprintk("svc: server %p waiting for data (to = %ld)\n",
                rqstp, timeout);
                add_wait_queue(&rqstp->rq_wait, &wait);
                spin_unlock_bh(&pool->sp_lock);
 
-               schedule_timeout(timeout);
+               time_left = schedule_timeout(timeout);
 
                try_to_freeze();
 
                spin_lock_bh(&pool->sp_lock);
                remove_wait_queue(&rqstp->rq_wait, &wait);
+               if (!time_left)
+                       pool->sp_stats.threads_timedout++;
 
                xprt = rqstp->rq_xprt;
                if (!xprt) {
        return totlen;
 }
 EXPORT_SYMBOL_GPL(svc_xprt_names);
+
+
+/*----------------------------------------------------------------------------*/
+
+static void *svc_pool_stats_start(struct seq_file *m, loff_t *pos)
+{
+       unsigned int pidx = (unsigned int)*pos;
+       struct svc_serv *serv = m->private;
+
+       dprintk("svc_pool_stats_start, *pidx=%u\n", pidx);
+
+       lock_kernel();
+       /* bump up the pseudo refcount while traversing */
+       svc_get(serv);
+       unlock_kernel();
+
+       if (!pidx)
+               return SEQ_START_TOKEN;
+       return (pidx > serv->sv_nrpools ? NULL : &serv->sv_pools[pidx-1]);
+}
+
+static void *svc_pool_stats_next(struct seq_file *m, void *p, loff_t *pos)
+{
+       struct svc_pool *pool = p;
+       struct svc_serv *serv = m->private;
+
+       dprintk("svc_pool_stats_next, *pos=%llu\n", *pos);
+
+       if (p == SEQ_START_TOKEN) {
+               pool = &serv->sv_pools[0];
+       } else {
+               unsigned int pidx = (pool - &serv->sv_pools[0]);
+               if (pidx < serv->sv_nrpools-1)
+                       pool = &serv->sv_pools[pidx+1];
+               else
+                       pool = NULL;
+       }
+       ++*pos;
+       return pool;
+}
+
+static void svc_pool_stats_stop(struct seq_file *m, void *p)
+{
+       struct svc_serv *serv = m->private;
+
+       lock_kernel();
+       /* this function really, really should have been called svc_put() */
+       svc_destroy(serv);
+       unlock_kernel();
+}
+
+static int svc_pool_stats_show(struct seq_file *m, void *p)
+{
+       struct svc_pool *pool = p;
+
+       if (p == SEQ_START_TOKEN) {
+               seq_puts(m, "# pool packets-arrived sockets-enqueued threads-woken overloads-avoided threads-timedout\n");
+               return 0;
+       }
+
+       seq_printf(m, "%u %lu %lu %lu %lu %lu\n",
+               pool->sp_id,
+               pool->sp_stats.packets,
+               pool->sp_stats.sockets_queued,
+               pool->sp_stats.threads_woken,
+               pool->sp_stats.overloads_avoided,
+               pool->sp_stats.threads_timedout);
+
+       return 0;
+}
+
+static const struct seq_operations svc_pool_stats_seq_ops = {
+       .start  = svc_pool_stats_start,
+       .next   = svc_pool_stats_next,
+       .stop   = svc_pool_stats_stop,
+       .show   = svc_pool_stats_show,
+};
+
+int svc_pool_stats_open(struct svc_serv *serv, struct file *file)
+{
+       int err;
+
+       err = seq_open(file, &svc_pool_stats_seq_ops);
+       if (!err)
+               ((struct seq_file *) file->private_data)->private = serv;
+       return err;
+}
+EXPORT_SYMBOL(svc_pool_stats_open);
+
+/*----------------------------------------------------------------------------*/