Skip to content

Commit dde9588

Browse files
Dmitry Monakhovjankara
authored andcommitted
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: Dmitry Monakhov <[email protected]> Signed-off-by: Jan Kara <[email protected]>
1 parent da8d1ba commit dde9588

File tree

4 files changed

+106
-46
lines changed

4 files changed

+106
-46
lines changed

fs/quota/dquot.c

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282

8383
/*
8484
* There are three quota SMP locks. dq_list_lock protects all lists with quotas
85-
* and quota formats, dqstats structure containing statistics about the lists
85+
* and quota formats.
8686
* dq_data_lock protects data from dq_dqb and also mem_dqinfo structures and
8787
* also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes.
8888
* i_blocks and i_bytes updates itself are guarded by i_lock acquired directly
@@ -228,6 +228,10 @@ static struct hlist_head *dquot_hash;
228228

229229
struct dqstats dqstats;
230230
EXPORT_SYMBOL(dqstats);
231+
#ifdef CONFIG_SMP
232+
struct dqstats *dqstats_pcpu;
233+
EXPORT_SYMBOL(dqstats_pcpu);
234+
#endif
231235

232236
static qsize_t inode_get_rsv_space(struct inode *inode);
233237
static void __dquot_initialize(struct inode *inode, int type);
@@ -275,28 +279,28 @@ static struct dquot *find_dquot(unsigned int hashent, struct super_block *sb,
275279
static inline void put_dquot_last(struct dquot *dquot)
276280
{
277281
list_add_tail(&dquot->dq_free, &free_dquots);
278-
dqstats.free_dquots++;
282+
dqstats_inc(DQST_FREE_DQUOTS);
279283
}
280284

281285
static inline void remove_free_dquot(struct dquot *dquot)
282286
{
283287
if (list_empty(&dquot->dq_free))
284288
return;
285289
list_del_init(&dquot->dq_free);
286-
dqstats.free_dquots--;
290+
dqstats_dec(DQST_FREE_DQUOTS);
287291
}
288292

289293
static inline void put_inuse(struct dquot *dquot)
290294
{
291295
/* We add to the back of inuse list so we don't have to restart
292296
* when traversing this list and we block */
293297
list_add_tail(&dquot->dq_inuse, &inuse_list);
294-
dqstats.allocated_dquots++;
298+
dqstats_inc(DQST_ALLOC_DQUOTS);
295299
}
296300

297301
static inline void remove_inuse(struct dquot *dquot)
298302
{
299-
dqstats.allocated_dquots--;
303+
dqstats_dec(DQST_ALLOC_DQUOTS);
300304
list_del(&dquot->dq_inuse);
301305
}
302306
/*
@@ -561,8 +565,8 @@ int dquot_scan_active(struct super_block *sb,
561565
continue;
562566
/* Now we have active dquot so we can just increase use count */
563567
atomic_inc(&dquot->dq_count);
564-
dqstats.lookups++;
565568
spin_unlock(&dq_list_lock);
569+
dqstats_inc(DQST_LOOKUPS);
566570
dqput(old_dquot);
567571
old_dquot = dquot;
568572
ret = fn(dquot, priv);
@@ -607,8 +611,8 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait)
607611
* holding reference so we can safely just increase
608612
* use count */
609613
atomic_inc(&dquot->dq_count);
610-
dqstats.lookups++;
611614
spin_unlock(&dq_list_lock);
615+
dqstats_inc(DQST_LOOKUPS);
612616
sb->dq_op->write_dquot(dquot);
613617
dqput(dquot);
614618
spin_lock(&dq_list_lock);
@@ -620,9 +624,7 @@ int vfs_quota_sync(struct super_block *sb, int type, int wait)
620624
if ((cnt == type || type == -1) && sb_has_quota_active(sb, cnt)
621625
&& info_dirty(&dqopt->info[cnt]))
622626
sb->dq_op->write_info(sb, cnt);
623-
spin_lock(&dq_list_lock);
624-
dqstats.syncs++;
625-
spin_unlock(&dq_list_lock);
627+
dqstats_inc(DQST_SYNCS);
626628
mutex_unlock(&dqopt->dqonoff_mutex);
627629

628630
if (!wait || (sb_dqopt(sb)->flags & DQUOT_QUOTA_SYS_FILE))
@@ -674,6 +676,22 @@ static void prune_dqcache(int count)
674676
}
675677
}
676678

679+
static int dqstats_read(unsigned int type)
680+
{
681+
int count = 0;
682+
#ifdef CONFIG_SMP
683+
int cpu;
684+
for_each_possible_cpu(cpu)
685+
count += per_cpu_ptr(dqstats_pcpu, cpu)->stat[type];
686+
/* Statistics reading is racy, but absolute accuracy isn't required */
687+
if (count < 0)
688+
count = 0;
689+
#else
690+
count = dqstats.stat[type];
691+
#endif
692+
return count;
693+
}
694+
677695
/*
678696
* This is called from kswapd when we think we need some
679697
* more memory
@@ -686,7 +704,7 @@ static int shrink_dqcache_memory(int nr, gfp_t gfp_mask)
686704
prune_dqcache(nr);
687705
spin_unlock(&dq_list_lock);
688706
}
689-
return (dqstats.free_dquots / 100) * sysctl_vfs_cache_pressure;
707+
return (dqstats_read(DQST_FREE_DQUOTS)/100) * sysctl_vfs_cache_pressure;
690708
}
691709

692710
static struct shrinker dqcache_shrinker = {
@@ -714,10 +732,7 @@ void dqput(struct dquot *dquot)
714732
BUG();
715733
}
716734
#endif
717-
718-
spin_lock(&dq_list_lock);
719-
dqstats.drops++;
720-
spin_unlock(&dq_list_lock);
735+
dqstats_inc(DQST_DROPS);
721736
we_slept:
722737
spin_lock(&dq_list_lock);
723738
if (atomic_read(&dquot->dq_count) > 1) {
@@ -834,15 +849,15 @@ struct dquot *dqget(struct super_block *sb, unsigned int id, int type)
834849
put_inuse(dquot);
835850
/* hash it first so it can be found */
836851
insert_dquot_hash(dquot);
837-
dqstats.lookups++;
838852
spin_unlock(&dq_list_lock);
853+
dqstats_inc(DQST_LOOKUPS);
839854
} else {
840855
if (!atomic_read(&dquot->dq_count))
841856
remove_free_dquot(dquot);
842857
atomic_inc(&dquot->dq_count);
843-
dqstats.cache_hits++;
844-
dqstats.lookups++;
845858
spin_unlock(&dq_list_lock);
859+
dqstats_inc(DQST_CACHE_HITS);
860+
dqstats_inc(DQST_LOOKUPS);
846861
}
847862
/* Wait for dq_lock - after this we know that either dquot_release() is
848863
* already finished or it will be canceled due to dq_count > 1 test */
@@ -2476,62 +2491,74 @@ const struct quotactl_ops vfs_quotactl_ops = {
24762491
.set_dqblk = vfs_set_dqblk
24772492
};
24782493

2494+
2495+
static int do_proc_dqstats(struct ctl_table *table, int write,
2496+
void __user *buffer, size_t *lenp, loff_t *ppos)
2497+
{
2498+
#ifdef CONFIG_SMP
2499+
/* Update global table */
2500+
unsigned int type = (int *)table->data - dqstats.stat;
2501+
dqstats.stat[type] = dqstats_read(type);
2502+
#endif
2503+
return proc_dointvec(table, write, buffer, lenp, ppos);
2504+
}
2505+
24792506
static ctl_table fs_dqstats_table[] = {
24802507
{
24812508
.procname = "lookups",
2482-
.data = &dqstats.lookups,
2509+
.data = &dqstats.stat[DQST_LOOKUPS],
24832510
.maxlen = sizeof(int),
24842511
.mode = 0444,
2485-
.proc_handler = proc_dointvec,
2512+
.proc_handler = do_proc_dqstats,
24862513
},
24872514
{
24882515
.procname = "drops",
2489-
.data = &dqstats.drops,
2516+
.data = &dqstats.stat[DQST_DROPS],
24902517
.maxlen = sizeof(int),
24912518
.mode = 0444,
2492-
.proc_handler = proc_dointvec,
2519+
.proc_handler = do_proc_dqstats,
24932520
},
24942521
{
24952522
.procname = "reads",
2496-
.data = &dqstats.reads,
2523+
.data = &dqstats.stat[DQST_READS],
24972524
.maxlen = sizeof(int),
24982525
.mode = 0444,
2499-
.proc_handler = proc_dointvec,
2526+
.proc_handler = do_proc_dqstats,
25002527
},
25012528
{
25022529
.procname = "writes",
2503-
.data = &dqstats.writes,
2530+
.data = &dqstats.stat[DQST_WRITES],
25042531
.maxlen = sizeof(int),
25052532
.mode = 0444,
2506-
.proc_handler = proc_dointvec,
2533+
.proc_handler = do_proc_dqstats,
25072534
},
25082535
{
25092536
.procname = "cache_hits",
2510-
.data = &dqstats.cache_hits,
2537+
.data = &dqstats.stat[DQST_CACHE_HITS],
25112538
.maxlen = sizeof(int),
25122539
.mode = 0444,
2513-
.proc_handler = proc_dointvec,
2540+
.proc_handler = do_proc_dqstats,
25142541
},
25152542
{
25162543
.procname = "allocated_dquots",
2517-
.data = &dqstats.allocated_dquots,
2544+
.data = &dqstats.stat[DQST_ALLOC_DQUOTS],
25182545
.maxlen = sizeof(int),
25192546
.mode = 0444,
2520-
.proc_handler = proc_dointvec,
2547+
.proc_handler = do_proc_dqstats,
25212548
},
25222549
{
25232550
.procname = "free_dquots",
2524-
.data = &dqstats.free_dquots,
2551+
.data = &dqstats.stat[DQST_FREE_DQUOTS],
25252552
.maxlen = sizeof(int),
25262553
.mode = 0444,
2527-
.proc_handler = proc_dointvec,
2554+
.proc_handler = do_proc_dqstats,
25282555
},
25292556
{
25302557
.procname = "syncs",
2531-
.data = &dqstats.syncs,
2558+
.data = &dqstats.stat[DQST_SYNCS],
25322559
.maxlen = sizeof(int),
25332560
.mode = 0444,
2534-
.proc_handler = proc_dointvec,
2561+
.proc_handler = do_proc_dqstats,
25352562
},
25362563
#ifdef CONFIG_PRINT_QUOTA_WARNING
25372564
{
@@ -2583,6 +2610,13 @@ static int __init dquot_init(void)
25832610
if (!dquot_hash)
25842611
panic("Cannot create dquot hash table");
25852612

2613+
#ifdef CONFIG_SMP
2614+
dqstats_pcpu = alloc_percpu(struct dqstats);
2615+
if (!dqstats_pcpu)
2616+
panic("Cannot create dquot stats table");
2617+
#endif
2618+
memset(&dqstats, 0, sizeof(struct dqstats));
2619+
25862620
/* Find power-of-two hlist_heads which can fit into allocation */
25872621
nr_hash = (1UL << order) * PAGE_SIZE / sizeof(struct hlist_head);
25882622
dq_hash_bits = 0;

fs/quota/quota_tree.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
384384
} else {
385385
ret = 0;
386386
}
387-
dqstats.writes++;
387+
dqstats_inc(DQST_WRITES);
388388
kfree(ddquot);
389389

390390
return ret;
@@ -634,7 +634,7 @@ int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
634634
spin_unlock(&dq_data_lock);
635635
kfree(ddquot);
636636
out:
637-
dqstats.reads++;
637+
dqstats_inc(DQST_READS);
638638
return ret;
639639
}
640640
EXPORT_SYMBOL(qtree_read_dquot);

fs/quota/quota_v1.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ static int v1_read_dqblk(struct dquot *dquot)
7171
dquot->dq_dqb.dqb_ihardlimit == 0 &&
7272
dquot->dq_dqb.dqb_isoftlimit == 0)
7373
set_bit(DQ_FAKE_B, &dquot->dq_flags);
74-
dqstats.reads++;
74+
dqstats_inc(DQST_READS);
7575

7676
return 0;
7777
}
@@ -104,7 +104,7 @@ static int v1_commit_dqblk(struct dquot *dquot)
104104
ret = 0;
105105

106106
out:
107-
dqstats.writes++;
107+
dqstats_inc(DQST_WRITES);
108108

109109
return ret;
110110
}

include/linux/quota.h

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ enum {
174174
#include <linux/rwsem.h>
175175
#include <linux/spinlock.h>
176176
#include <linux/wait.h>
177+
#include <linux/percpu.h>
178+
#include <linux/smp.h>
177179

178180
#include <linux/dqblk_xfs.h>
179181
#include <linux/dqblk_v1.h>
@@ -238,19 +240,43 @@ static inline int info_dirty(struct mem_dqinfo *info)
238240
return test_bit(DQF_INFO_DIRTY_B, &info->dqi_flags);
239241
}
240242

243+
enum {
244+
DQST_LOOKUPS,
245+
DQST_DROPS,
246+
DQST_READS,
247+
DQST_WRITES,
248+
DQST_CACHE_HITS,
249+
DQST_ALLOC_DQUOTS,
250+
DQST_FREE_DQUOTS,
251+
DQST_SYNCS,
252+
_DQST_DQSTAT_LAST
253+
};
254+
241255
struct dqstats {
242-
int lookups;
243-
int drops;
244-
int reads;
245-
int writes;
246-
int cache_hits;
247-
int allocated_dquots;
248-
int free_dquots;
249-
int syncs;
256+
int stat[_DQST_DQSTAT_LAST];
250257
};
251258

259+
extern struct dqstats *dqstats_pcpu;
252260
extern struct dqstats dqstats;
253261

262+
static inline void dqstats_inc(unsigned int type)
263+
{
264+
#ifdef CONFIG_SMP
265+
per_cpu_ptr(dqstats_pcpu, smp_processor_id())->stat[type]++;
266+
#else
267+
dqstats.stat[type]++;
268+
#endif
269+
}
270+
271+
static inline void dqstats_dec(unsigned int type)
272+
{
273+
#ifdef CONFIG_SMP
274+
per_cpu_ptr(dqstats_pcpu, smp_processor_id())->stat[type]--;
275+
#else
276+
dqstats.stat[type]--;
277+
#endif
278+
}
279+
254280
#define DQ_MOD_B 0 /* dquot modified since read */
255281
#define DQ_BLKS_B 1 /* uid/gid has been warned about blk limit */
256282
#define DQ_INODES_B 2 /* uid/gid has been warned about inode limit */

0 commit comments

Comments
 (0)