Skip to content

Commit

Permalink
nfsd: protect concurrent access to nfsd stats counters
Browse files Browse the repository at this point in the history
nfsd stats counters can be updated by concurrent nfsd threads without any
protection.

Convert some nfsd_stats and nfsd_net struct members to use percpu counters.

The longest_chain* members of struct nfsd_net remain unprotected.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
  • Loading branch information
Amir Goldstein authored and Chuck Lever committed Jan 25, 2021
1 parent 1b76d1d commit e567b98
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 53 deletions.
23 changes: 14 additions & 9 deletions fs/nfsd/netns.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <linux/percpu_counter.h>

/* Hash tables for nfs4_clientid state */
#define CLIENT_HASH_BITS 4
Expand All @@ -21,6 +22,14 @@
struct cld_net;
struct nfsd4_client_tracking_ops;

enum {
/* cache misses due only to checksum comparison failures */
NFSD_NET_PAYLOAD_MISSES,
/* amount of memory (in bytes) currently consumed by the DRC */
NFSD_NET_DRC_MEM_USAGE,
NFSD_NET_COUNTERS_NUM
};

/*
* Represents a nfsd "container". With respect to nfsv4 state tracking, the
* fields of interest are the *_id_hashtbls and the *_name_tree. These track
Expand Down Expand Up @@ -149,20 +158,16 @@ struct nfsd_net {

/*
* Stats and other tracking of on the duplicate reply cache.
* These fields and the "rc" fields in nfsdstats are modified
* with only the per-bucket cache lock, which isn't really safe
* and should be fixed if we want the statistics to be
* completely accurate.
* The longest_chain* fields are modified with only the per-bucket
* cache lock, which isn't really safe and should be fixed if we want
* these statistics to be completely accurate.
*/

/* total number of entries */
atomic_t num_drc_entries;

/* cache misses due only to checksum comparison failures */
unsigned int payload_misses;

/* amount of memory (in bytes) currently consumed by the DRC */
unsigned int drc_mem_usage;
/* Per-netns stats counters */
struct percpu_counter counter[NFSD_NET_COUNTERS_NUM];

/* longest hash chain seen */
unsigned int longest_chain;
Expand Down
2 changes: 1 addition & 1 deletion fs/nfsd/nfs4proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2174,7 +2174,7 @@ nfsd4_proc_null(struct svc_rqst *rqstp)
static inline void nfsd4_increment_op_stats(u32 opnum)
{
if (opnum >= FIRST_NFS4_OP && opnum <= LAST_NFS4_OP)
nfsdstats.nfs4_opcount[opnum]++;
percpu_counter_inc(&nfsdstats.counter[NFSD_STATS_NFS4_OP(opnum)]);
}

static const struct nfsd4_operation nfsd4_ops[];
Expand Down
52 changes: 37 additions & 15 deletions fs/nfsd/nfscache.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ nfsd_reply_cache_free_locked(struct nfsd_drc_bucket *b, struct svc_cacherep *rp,
struct nfsd_net *nn)
{
if (rp->c_type == RC_REPLBUFF && rp->c_replvec.iov_base) {
nn->drc_mem_usage -= rp->c_replvec.iov_len;
nfsd_stats_drc_mem_usage_sub(nn, rp->c_replvec.iov_len);
kfree(rp->c_replvec.iov_base);
}
if (rp->c_state != RC_UNUSED) {
rb_erase(&rp->c_node, &b->rb_head);
list_del(&rp->c_lru);
atomic_dec(&nn->num_drc_entries);
nn->drc_mem_usage -= sizeof(*rp);
nfsd_stats_drc_mem_usage_sub(nn, sizeof(*rp));
}
kmem_cache_free(drc_slab, rp);
}
Expand All @@ -154,6 +154,16 @@ void nfsd_drc_slab_free(void)
kmem_cache_destroy(drc_slab);
}

static int nfsd_reply_cache_stats_init(struct nfsd_net *nn)
{
return nfsd_percpu_counters_init(nn->counter, NFSD_NET_COUNTERS_NUM);
}

static void nfsd_reply_cache_stats_destroy(struct nfsd_net *nn)
{
nfsd_percpu_counters_destroy(nn->counter, NFSD_NET_COUNTERS_NUM);
}

int nfsd_reply_cache_init(struct nfsd_net *nn)
{
unsigned int hashsize;
Expand All @@ -165,12 +175,16 @@ int nfsd_reply_cache_init(struct nfsd_net *nn)
hashsize = nfsd_hashsize(nn->max_drc_entries);
nn->maskbits = ilog2(hashsize);

status = nfsd_reply_cache_stats_init(nn);
if (status)
goto out_nomem;

nn->nfsd_reply_cache_shrinker.scan_objects = nfsd_reply_cache_scan;
nn->nfsd_reply_cache_shrinker.count_objects = nfsd_reply_cache_count;
nn->nfsd_reply_cache_shrinker.seeks = 1;
status = register_shrinker(&nn->nfsd_reply_cache_shrinker);
if (status)
goto out_nomem;
goto out_stats_destroy;

nn->drc_hashtbl = kvzalloc(array_size(hashsize,
sizeof(*nn->drc_hashtbl)), GFP_KERNEL);
Expand All @@ -186,6 +200,8 @@ int nfsd_reply_cache_init(struct nfsd_net *nn)
return 0;
out_shrinker:
unregister_shrinker(&nn->nfsd_reply_cache_shrinker);
out_stats_destroy:
nfsd_reply_cache_stats_destroy(nn);
out_nomem:
printk(KERN_ERR "nfsd: failed to allocate reply cache\n");
return -ENOMEM;
Expand All @@ -196,6 +212,7 @@ void nfsd_reply_cache_shutdown(struct nfsd_net *nn)
struct svc_cacherep *rp;
unsigned int i;

nfsd_reply_cache_stats_destroy(nn);
unregister_shrinker(&nn->nfsd_reply_cache_shrinker);

for (i = 0; i < nn->drc_hashsize; i++) {
Expand Down Expand Up @@ -324,7 +341,7 @@ nfsd_cache_key_cmp(const struct svc_cacherep *key,
{
if (key->c_key.k_xid == rp->c_key.k_xid &&
key->c_key.k_csum != rp->c_key.k_csum) {
++nn->payload_misses;
nfsd_stats_payload_misses_inc(nn);
trace_nfsd_drc_mismatch(nn, key, rp);
}

Expand Down Expand Up @@ -407,7 +424,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp)

rqstp->rq_cacherep = NULL;
if (type == RC_NOCACHE) {
nfsdstats.rcnocache++;
nfsd_stats_rc_nocache_inc();
goto out;
}

Expand All @@ -429,12 +446,12 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp)
goto found_entry;
}

nfsdstats.rcmisses++;
nfsd_stats_rc_misses_inc();
rqstp->rq_cacherep = rp;
rp->c_state = RC_INPROG;

atomic_inc(&nn->num_drc_entries);
nn->drc_mem_usage += sizeof(*rp);
nfsd_stats_drc_mem_usage_add(nn, sizeof(*rp));

/* go ahead and prune the cache */
prune_bucket(b, nn);
Expand All @@ -446,7 +463,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp)

found_entry:
/* We found a matching entry which is either in progress or done. */
nfsdstats.rchits++;
nfsd_stats_rc_hits_inc();
rtn = RC_DROPIT;

/* Request being processed */
Expand Down Expand Up @@ -548,7 +565,7 @@ void nfsd_cache_update(struct svc_rqst *rqstp, int cachetype, __be32 *statp)
return;
}
spin_lock(&b->cache_lock);
nn->drc_mem_usage += bufsize;
nfsd_stats_drc_mem_usage_add(nn, bufsize);
lru_put_end(b, rp);
rp->c_secure = test_bit(RQ_SECURE, &rqstp->rq_flags);
rp->c_type = cachetype;
Expand Down Expand Up @@ -588,13 +605,18 @@ static int nfsd_reply_cache_stats_show(struct seq_file *m, void *v)

seq_printf(m, "max entries: %u\n", nn->max_drc_entries);
seq_printf(m, "num entries: %u\n",
atomic_read(&nn->num_drc_entries));
atomic_read(&nn->num_drc_entries));
seq_printf(m, "hash buckets: %u\n", 1 << nn->maskbits);
seq_printf(m, "mem usage: %u\n", nn->drc_mem_usage);
seq_printf(m, "cache hits: %u\n", nfsdstats.rchits);
seq_printf(m, "cache misses: %u\n", nfsdstats.rcmisses);
seq_printf(m, "not cached: %u\n", nfsdstats.rcnocache);
seq_printf(m, "payload misses: %u\n", nn->payload_misses);
seq_printf(m, "mem usage: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_NET_DRC_MEM_USAGE]));
seq_printf(m, "cache hits: %lld\n",
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_HITS]));
seq_printf(m, "cache misses: %lld\n",
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_MISSES]));
seq_printf(m, "not cached: %lld\n",
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_NOCACHE]));
seq_printf(m, "payload misses: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_NET_PAYLOAD_MISSES]));
seq_printf(m, "longest chain len: %u\n", nn->longest_chain);
seq_printf(m, "cachesize at longest: %u\n", nn->longest_chain_cachesize);
return 0;
Expand Down
5 changes: 4 additions & 1 deletion fs/nfsd/nfsctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1534,7 +1534,9 @@ static int __init init_nfsd(void)
retval = nfsd4_init_pnfs();
if (retval)
goto out_free_slabs;
nfsd_stat_init(); /* Statistics */
retval = nfsd_stat_init(); /* Statistics */
if (retval)
goto out_free_pnfs;
retval = nfsd_drc_slab_create();
if (retval)
goto out_free_stat;
Expand All @@ -1554,6 +1556,7 @@ static int __init init_nfsd(void)
nfsd_drc_slab_free();
out_free_stat:
nfsd_stat_shutdown();
out_free_pnfs:
nfsd4_exit_pnfs();
out_free_slabs:
nfsd4_free_slabs();
Expand Down
2 changes: 1 addition & 1 deletion fs/nfsd/nfsfh.c
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
}
out:
if (error == nfserr_stale)
nfsdstats.fh_stale++;
nfsd_stats_fh_stale_inc();
return error;
}

Expand Down
77 changes: 64 additions & 13 deletions fs/nfsd/stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ static int nfsd_proc_show(struct seq_file *seq, void *v)
{
int i;

seq_printf(seq, "rc %u %u %u\nfh %u 0 0 0 0\nio %u %u\n",
nfsdstats.rchits,
nfsdstats.rcmisses,
nfsdstats.rcnocache,
nfsdstats.fh_stale,
nfsdstats.io_read,
nfsdstats.io_write);
seq_printf(seq, "rc %lld %lld %lld\nfh %lld 0 0 0 0\nio %lld %lld\n",
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_HITS]),
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_MISSES]),
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_RC_NOCACHE]),
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_FH_STALE]),
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_IO_READ]),
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_IO_WRITE]));

/* thread usage: */
seq_printf(seq, "th %u 0", nfsdstats.th_cnt);
Expand All @@ -61,8 +61,10 @@ static int nfsd_proc_show(struct seq_file *seq, void *v)
/* Show count for individual nfsv4 operations */
/* Writing operation numbers 0 1 2 also for maintaining uniformity */
seq_printf(seq,"proc4ops %u", LAST_NFS4_OP + 1);
for (i = 0; i <= LAST_NFS4_OP; i++)
seq_printf(seq, " %u", nfsdstats.nfs4_opcount[i]);
for (i = 0; i <= LAST_NFS4_OP; i++) {
seq_printf(seq, " %lld",
percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_NFS4_OP(i)]));
}

seq_putc(seq, '\n');
#endif
Expand All @@ -82,14 +84,63 @@ static const struct proc_ops nfsd_proc_ops = {
.proc_release = single_release,
};

void
nfsd_stat_init(void)
int nfsd_percpu_counters_init(struct percpu_counter counters[], int num)
{
int i, err = 0;

for (i = 0; !err && i < num; i++)
err = percpu_counter_init(&counters[i], 0, GFP_KERNEL);

if (!err)
return 0;

for (; i > 0; i--)
percpu_counter_destroy(&counters[i-1]);

return err;
}

void nfsd_percpu_counters_reset(struct percpu_counter counters[], int num)
{
int i;

for (i = 0; i < num; i++)
percpu_counter_set(&counters[i], 0);
}

void nfsd_percpu_counters_destroy(struct percpu_counter counters[], int num)
{
int i;

for (i = 0; i < num; i++)
percpu_counter_destroy(&counters[i]);
}

static int nfsd_stat_counters_init(void)
{
return nfsd_percpu_counters_init(nfsdstats.counter, NFSD_STATS_COUNTERS_NUM);
}

static void nfsd_stat_counters_destroy(void)
{
nfsd_percpu_counters_destroy(nfsdstats.counter, NFSD_STATS_COUNTERS_NUM);
}

int nfsd_stat_init(void)
{
int err;

err = nfsd_stat_counters_init();
if (err)
return err;

svc_proc_register(&init_net, &nfsd_svcstats, &nfsd_proc_ops);

return 0;
}

void
nfsd_stat_shutdown(void)
void nfsd_stat_shutdown(void)
{
nfsd_stat_counters_destroy();
svc_proc_unregister(&init_net, "nfsd");
}
Loading

0 comments on commit e567b98

Please sign in to comment.