Commit dde95888 authored by Dmitry Monakhov's avatar Dmitry Monakhov Committed by Jan Kara

quota: Make quota stat accounting lockless.

Quota stats is mostly writable data structure. Let's alloc percpu
bucket for each value.

NOTE: dqstats_read() function is racy against dqstats_{inc,dec}
and may return inconsistent value. But this is ok since absolute
accuracy is not required.
Signed-off-by: default avatarDmitry Monakhov <dmonakhov@openvz.org>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
parent da8d1ba2
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
/* /*
* There are three quota SMP locks. dq_list_lock protects all lists with quotas * There are three quota SMP locks. dq_list_lock protects all lists with quotas
* and quota formats, dqstats structure containing statistics about the lists * and quota formats.
* dq_data_lock protects data from dq_dqb and also mem_dqinfo structures and * dq_data_lock protects data from dq_dqb and also mem_dqinfo structures and
* also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes. * also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes.
* i_blocks and i_bytes updates itself are guarded by i_lock acquired directly * i_blocks and i_bytes updates itself are guarded by i_lock acquired directly
...@@ -228,6 +228,10 @@ static struct hlist_head *dquot_hash; ...@@ -228,6 +228,10 @@ static struct hlist_head *dquot_hash;
struct dqstats dqstats; struct dqstats dqstats;
EXPORT_SYMBOL(dqstats); EXPORT_SYMBOL(dqstats);
#ifdef CONFIG_SMP
struct dqstats *dqstats_pcpu;
EXPORT_SYMBOL(dqstats_pcpu);
#endif
static qsize_t inode_get_rsv_space(struct inode *inode); static qsize_t inode_get_rsv_space(struct inode *inode);
static void __dquot_initialize(struct inode *inode, int type); static void __dquot_initialize(struct inode *inode, int type);
...@@ -275,7 +279,7 @@ static struct dquot *find_dquot(unsigned int hashent, struct super_block *sb, ...@@ -275,7 +279,7 @@ static struct dquot *find_dquot(unsigned int hashent, struct super_block *sb,
static inline void put_dquot_last(struct dquot *dquot) static inline void put_dquot_last(struct dquot *dquot)
{ {
list_add_tail(&dquot->dq_free, &free_dquots); list_add_tail(&dquot->dq_free, &free_dquots);
dqstats.free_dquots++; dqstats_inc(DQST_FREE_DQUOTS);
} }
static inline void remove_free_dquot(struct dquot *dquot) static inline void remove_free_dquot(struct dquot *dquot)
...@@ -283,7 +287,7 @@ static inline void remove_free_dquot(struct dquot *dquot) ...@@ -283,7 +287,7 @@ static inline void remove_free_dquot(struct dquot *dquot)
if (list_empty(&dquot->dq_free)) if (list_empty(&dquot->dq_free))
return; return;
list_del_init(&dquot->dq_free); list_del_init(&dquot->dq_free);
dqstats.free_dquots--; dqstats_dec(DQST_FREE_DQUOTS);
} }
static inline void put_inuse(struct dquot *dquot) static inline void put_inuse(struct dquot *dquot)
...@@ -291,12 +295,12 @@ static inline void put_inuse(struct dquot *dquot) ...@@ -291,12 +295,12 @@ static inline void put_inuse(struct dquot *dquot)
/* We add to the back of inuse list so we don't have to restart /* We add to the back of inuse list so we don't have to restart
* when traversing this list and we block */ * when traversing this list and we block */
list_add_tail(&dquot->dq_inuse, &inuse_list); list_add_tail(&dquot->dq_inuse, &inuse_list);
dqstats.allocated_dquots++; dqstats_inc(DQST_ALLOC_DQUOTS);
} }
static inline void remove_inuse(struct dquot *dquot) static inline void remove_inuse(struct dquot *dquot)
{ {
dqstats.allocated_dquots--; dqstats_dec(DQST_ALLOC_DQUOTS);
list_del(&dquot->dq_inuse); list_del(&dquot->dq_inuse);
} }
/* /*
...@@ -561,8 +565,8 @@ int dquot_scan_active(struct super_block *sb, ...@@ -561,8 +565,8 @@ int dquot_scan_active(struct super_block *sb,
continue; continue;
/* Now we have active dquot so we can just increase use count */ /* Now we have active dquot so we can just increase use count */
atomic_inc(&dquot->dq_count); atomic_inc(&dquot->dq_count);
dqstats.lookups++;
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
dqstats_inc(DQST_LOOKUPS);
dqput(old_dquot); dqput(old_dquot);
old_dquot = dquot; old_dquot = dquot;
ret = fn(dquot, priv); ret = fn(dquot, priv);
...@@ -607,8 +611,8 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait) ...@@ -607,8 +611,8 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait)
* holding reference so we can safely just increase * holding reference so we can safely just increase
* use count */ * use count */
atomic_inc(&dquot->dq_count); atomic_inc(&dquot->dq_count);
dqstats.lookups++;
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
dqstats_inc(DQST_LOOKUPS);
sb->dq_op->write_dquot(dquot); sb->dq_op->write_dquot(dquot);
dqput(dquot); dqput(dquot);
spin_lock(&dq_list_lock); spin_lock(&dq_list_lock);
...@@ -620,9 +624,7 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait) ...@@ -620,9 +624,7 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait)
if ((cnt == type || type == -1) && sb_has_quota_active(sb, cnt) if ((cnt == type || type == -1) && sb_has_quota_active(sb, cnt)
&& info_dirty(&dqopt->info[cnt])) && info_dirty(&dqopt->info[cnt]))
sb->dq_op->write_info(sb, cnt); sb->dq_op->write_info(sb, cnt);
spin_lock(&dq_list_lock); dqstats_inc(DQST_SYNCS);
dqstats.syncs++;
spin_unlock(&dq_list_lock);
mutex_unlock(&dqopt->dqonoff_mutex); mutex_unlock(&dqopt->dqonoff_mutex);
if (!wait || (sb_dqopt(sb)->flags & DQUOT_QUOTA_SYS_FILE)) if (!wait || (sb_dqopt(sb)->flags & DQUOT_QUOTA_SYS_FILE))
...@@ -674,6 +676,22 @@ static void prune_dqcache(int count) ...@@ -674,6 +676,22 @@ static void prune_dqcache(int count)
} }
} }
static int dqstats_read(unsigned int type)
{
int count = 0;
#ifdef CONFIG_SMP
int cpu;
for_each_possible_cpu(cpu)
count += per_cpu_ptr(dqstats_pcpu, cpu)->stat[type];
/* Statistics reading is racy, but absolute accuracy isn't required */
if (count < 0)
count = 0;
#else
count = dqstats.stat[type];
#endif
return count;
}
/* /*
* This is called from kswapd when we think we need some * This is called from kswapd when we think we need some
* more memory * more memory
...@@ -686,7 +704,7 @@ static int shrink_dqcache_memory(int nr, gfp_t gfp_mask) ...@@ -686,7 +704,7 @@ static int shrink_dqcache_memory(int nr, gfp_t gfp_mask)
prune_dqcache(nr); prune_dqcache(nr);
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
} }
return (dqstats.free_dquots / 100) * sysctl_vfs_cache_pressure; return (dqstats_read(DQST_FREE_DQUOTS)/100) * sysctl_vfs_cache_pressure;
} }
static struct shrinker dqcache_shrinker = { static struct shrinker dqcache_shrinker = {
...@@ -714,10 +732,7 @@ void dqput(struct dquot *dquot) ...@@ -714,10 +732,7 @@ void dqput(struct dquot *dquot)
BUG(); BUG();
} }
#endif #endif
dqstats_inc(DQST_DROPS);
spin_lock(&dq_list_lock);
dqstats.drops++;
spin_unlock(&dq_list_lock);
we_slept: we_slept:
spin_lock(&dq_list_lock); spin_lock(&dq_list_lock);
if (atomic_read(&dquot->dq_count) > 1) { if (atomic_read(&dquot->dq_count) > 1) {
...@@ -834,15 +849,15 @@ struct dquot *dqget(struct super_block *sb, unsigned int id, int type) ...@@ -834,15 +849,15 @@ struct dquot *dqget(struct super_block *sb, unsigned int id, int type)
put_inuse(dquot); put_inuse(dquot);
/* hash it first so it can be found */ /* hash it first so it can be found */
insert_dquot_hash(dquot); insert_dquot_hash(dquot);
dqstats.lookups++;
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
dqstats_inc(DQST_LOOKUPS);
} else { } else {
if (!atomic_read(&dquot->dq_count)) if (!atomic_read(&dquot->dq_count))
remove_free_dquot(dquot); remove_free_dquot(dquot);
atomic_inc(&dquot->dq_count); atomic_inc(&dquot->dq_count);
dqstats.cache_hits++;
dqstats.lookups++;
spin_unlock(&dq_list_lock); spin_unlock(&dq_list_lock);
dqstats_inc(DQST_CACHE_HITS);
dqstats_inc(DQST_LOOKUPS);
} }
/* Wait for dq_lock - after this we know that either dquot_release() is /* Wait for dq_lock - after this we know that either dquot_release() is
* already finished or it will be canceled due to dq_count > 1 test */ * already finished or it will be canceled due to dq_count > 1 test */
...@@ -2476,62 +2491,74 @@ const struct quotactl_ops vfs_quotactl_ops = { ...@@ -2476,62 +2491,74 @@ const struct quotactl_ops vfs_quotactl_ops = {
.set_dqblk = vfs_set_dqblk .set_dqblk = vfs_set_dqblk
}; };
static int do_proc_dqstats(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
#ifdef CONFIG_SMP
/* Update global table */
unsigned int type = (int *)table->data - dqstats.stat;
dqstats.stat[type] = dqstats_read(type);
#endif
return proc_dointvec(table, write, buffer, lenp, ppos);
}
static ctl_table fs_dqstats_table[] = { static ctl_table fs_dqstats_table[] = {
{ {
.procname = "lookups", .procname = "lookups",
.data = &dqstats.lookups, .data = &dqstats.stat[DQST_LOOKUPS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "drops", .procname = "drops",
.data = &dqstats.drops, .data = &dqstats.stat[DQST_DROPS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "reads", .procname = "reads",
.data = &dqstats.reads, .data = &dqstats.stat[DQST_READS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "writes", .procname = "writes",
.data = &dqstats.writes, .data = &dqstats.stat[DQST_WRITES],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "cache_hits", .procname = "cache_hits",
.data = &dqstats.cache_hits, .data = &dqstats.stat[DQST_CACHE_HITS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "allocated_dquots", .procname = "allocated_dquots",
.data = &dqstats.allocated_dquots, .data = &dqstats.stat[DQST_ALLOC_DQUOTS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "free_dquots", .procname = "free_dquots",
.data = &dqstats.free_dquots, .data = &dqstats.stat[DQST_FREE_DQUOTS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
{ {
.procname = "syncs", .procname = "syncs",
.data = &dqstats.syncs, .data = &dqstats.stat[DQST_SYNCS],
.maxlen = sizeof(int), .maxlen = sizeof(int),
.mode = 0444, .mode = 0444,
.proc_handler = proc_dointvec, .proc_handler = do_proc_dqstats,
}, },
#ifdef CONFIG_PRINT_QUOTA_WARNING #ifdef CONFIG_PRINT_QUOTA_WARNING
{ {
...@@ -2583,6 +2610,13 @@ static int __init dquot_init(void) ...@@ -2583,6 +2610,13 @@ static int __init dquot_init(void)
if (!dquot_hash) if (!dquot_hash)
panic("Cannot create dquot hash table"); panic("Cannot create dquot hash table");
#ifdef CONFIG_SMP
dqstats_pcpu = alloc_percpu(struct dqstats);
if (!dqstats_pcpu)
panic("Cannot create dquot stats table");
#endif
memset(&dqstats, 0, sizeof(struct dqstats));
/* Find power-of-two hlist_heads which can fit into allocation */ /* Find power-of-two hlist_heads which can fit into allocation */
nr_hash = (1UL << order) * PAGE_SIZE / sizeof(struct hlist_head); nr_hash = (1UL << order) * PAGE_SIZE / sizeof(struct hlist_head);
dq_hash_bits = 0; dq_hash_bits = 0;
......
...@@ -384,7 +384,7 @@ int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) ...@@ -384,7 +384,7 @@ int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
} else { } else {
ret = 0; ret = 0;
} }
dqstats.writes++; dqstats_inc(DQST_WRITES);
kfree(ddquot); kfree(ddquot);
return ret; return ret;
...@@ -634,7 +634,7 @@ int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) ...@@ -634,7 +634,7 @@ int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
spin_unlock(&dq_data_lock); spin_unlock(&dq_data_lock);
kfree(ddquot); kfree(ddquot);
out: out:
dqstats.reads++; dqstats_inc(DQST_READS);
return ret; return ret;
} }
EXPORT_SYMBOL(qtree_read_dquot); EXPORT_SYMBOL(qtree_read_dquot);
......
...@@ -71,7 +71,7 @@ static int v1_read_dqblk(struct dquot *dquot) ...@@ -71,7 +71,7 @@ static int v1_read_dqblk(struct dquot *dquot)
dquot->dq_dqb.dqb_ihardlimit == 0 && dquot->dq_dqb.dqb_ihardlimit == 0 &&
dquot->dq_dqb.dqb_isoftlimit == 0) dquot->dq_dqb.dqb_isoftlimit == 0)
set_bit(DQ_FAKE_B, &dquot->dq_flags); set_bit(DQ_FAKE_B, &dquot->dq_flags);
dqstats.reads++; dqstats_inc(DQST_READS);
return 0; return 0;
} }
...@@ -104,7 +104,7 @@ static int v1_commit_dqblk(struct dquot *dquot) ...@@ -104,7 +104,7 @@ static int v1_commit_dqblk(struct dquot *dquot)
ret = 0; ret = 0;
out: out:
dqstats.writes++; dqstats_inc(DQST_WRITES);
return ret; return ret;
} }
......
...@@ -174,6 +174,8 @@ enum { ...@@ -174,6 +174,8 @@ enum {
#include <linux/rwsem.h> #include <linux/rwsem.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/wait.h> #include <linux/wait.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/dqblk_xfs.h> #include <linux/dqblk_xfs.h>
#include <linux/dqblk_v1.h> #include <linux/dqblk_v1.h>
...@@ -238,19 +240,43 @@ static inline int info_dirty(struct mem_dqinfo *info) ...@@ -238,19 +240,43 @@ static inline int info_dirty(struct mem_dqinfo *info)
return test_bit(DQF_INFO_DIRTY_B, &info->dqi_flags); return test_bit(DQF_INFO_DIRTY_B, &info->dqi_flags);
} }
enum {
DQST_LOOKUPS,
DQST_DROPS,
DQST_READS,
DQST_WRITES,
DQST_CACHE_HITS,
DQST_ALLOC_DQUOTS,
DQST_FREE_DQUOTS,
DQST_SYNCS,
_DQST_DQSTAT_LAST
};
struct dqstats { struct dqstats {
int lookups; int stat[_DQST_DQSTAT_LAST];
int drops;
int reads;
int writes;
int cache_hits;
int allocated_dquots;
int free_dquots;
int syncs;
}; };
extern struct dqstats *dqstats_pcpu;
extern struct dqstats dqstats; extern struct dqstats dqstats;
static inline void dqstats_inc(unsigned int type)
{
#ifdef CONFIG_SMP
per_cpu_ptr(dqstats_pcpu, smp_processor_id())->stat[type]++;
#else
dqstats.stat[type]++;
#endif
}
static inline void dqstats_dec(unsigned int type)
{
#ifdef CONFIG_SMP
per_cpu_ptr(dqstats_pcpu, smp_processor_id())->stat[type]--;
#else
dqstats.stat[type]--;
#endif
}
#define DQ_MOD_B 0 /* dquot modified since read */ #define DQ_MOD_B 0 /* dquot modified since read */
#define DQ_BLKS_B 1 /* uid/gid has been warned about blk limit */ #define DQ_BLKS_B 1 /* uid/gid has been warned about blk limit */
#define DQ_INODES_B 2 /* uid/gid has been warned about inode limit */ #define DQ_INODES_B 2 /* uid/gid has been warned about inode limit */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment