fs/dcache: Track & report number of negative dentries

The current dentry number tracking code doesn't distinguish between
positive & negative dentries.  It just reports the total number of
dentries in the LRU lists.

As excessive number of negative dentries can have an impact on system
performance, it will be wise to track the number of positive and
negative dentries separately.

This patch adds tracking for the total number of negative dentries in
the system LRU lists and reports it in the 5th field in the
/proc/sys/fs/dentry-state file.  The number, however, does not include
negative dentries that are in flight but not in the LRU yet as well as
those in the shrinker lists which are on the way out anyway.

The number of positive dentries in the LRU lists can be roughly found by
subtracting the number of negative dentries from the unused count.

Matthew Wilcox had confirmed that since the introduction of the
dentry_stat structure in 2.1.60, the dummy array was there, probably for
future extension.  They were not replacements of pre-existing fields.
So no sane applications that read the value of /proc/sys/fs/dentry-state
will do dummy thing if the last 2 fields of the sysctl parameter are not
zero.  IOW, it will be safe to use one of the dummy array entry for
negative dentry count.

Signed-off-by: Waiman Long <longman@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Waiman Long 2019-01-30 13:52:38 -05:00 committed by Linus Torvalds
parent 7d10f70fc1
commit af0c9af1b3
3 changed files with 51 additions and 12 deletions

View File

@ -56,26 +56,32 @@ of any kernel data structures.
dentry-state: dentry-state:
From linux/fs/dentry.c: From linux/include/linux/dcache.h:
-------------------------------------------------------------- --------------------------------------------------------------
struct { struct dentry_stat_t dentry_stat {
int nr_dentry; int nr_dentry;
int nr_unused; int nr_unused;
int age_limit; /* age in seconds */ int age_limit; /* age in seconds */
int want_pages; /* pages requested by system */ int want_pages; /* pages requested by system */
int dummy[2]; int nr_negative; /* # of unused negative dentries */
} dentry_stat = {0, 0, 45, 0,}; int dummy; /* Reserved for future use */
};
-------------------------------------------------------------- --------------------------------------------------------------
Dentries are dynamically allocated and deallocated, and Dentries are dynamically allocated and deallocated.
nr_dentry seems to be 0 all the time. Hence it's safe to
assume that only nr_unused, age_limit and want_pages are nr_dentry shows the total number of dentries allocated (active
used. Nr_unused seems to be exactly what its name says. + unused). nr_unused shows the number of dentries that are not
actively used, but are saved in the LRU list for future reuse.
Age_limit is the age in seconds after which dcache entries Age_limit is the age in seconds after which dcache entries
can be reclaimed when memory is short and want_pages is can be reclaimed when memory is short and want_pages is
nonzero when shrink_dcache_pages() has been called and the nonzero when shrink_dcache_pages() has been called and the
dcache isn't pruned yet. dcache isn't pruned yet.
nr_negative shows the number of unused dentries that are also
negative dentries which do not mapped to actual files.
============================================================== ==============================================================
dquot-max & dquot-nr: dquot-max & dquot-nr:

View File

@ -119,6 +119,7 @@ struct dentry_stat_t dentry_stat = {
static DEFINE_PER_CPU(long, nr_dentry); static DEFINE_PER_CPU(long, nr_dentry);
static DEFINE_PER_CPU(long, nr_dentry_unused); static DEFINE_PER_CPU(long, nr_dentry_unused);
static DEFINE_PER_CPU(long, nr_dentry_negative);
#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS) #if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
@ -152,11 +153,22 @@ static long get_nr_dentry_unused(void)
return sum < 0 ? 0 : sum; return sum < 0 ? 0 : sum;
} }
static long get_nr_dentry_negative(void)
{
int i;
long sum = 0;
for_each_possible_cpu(i)
sum += per_cpu(nr_dentry_negative, i);
return sum < 0 ? 0 : sum;
}
int proc_nr_dentry(struct ctl_table *table, int write, void __user *buffer, int proc_nr_dentry(struct ctl_table *table, int write, void __user *buffer,
size_t *lenp, loff_t *ppos) size_t *lenp, loff_t *ppos)
{ {
dentry_stat.nr_dentry = get_nr_dentry(); dentry_stat.nr_dentry = get_nr_dentry();
dentry_stat.nr_unused = get_nr_dentry_unused(); dentry_stat.nr_unused = get_nr_dentry_unused();
dentry_stat.nr_negative = get_nr_dentry_negative();
return proc_doulongvec_minmax(table, write, buffer, lenp, ppos); return proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
} }
#endif #endif
@ -317,6 +329,8 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry)
flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU); flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU);
WRITE_ONCE(dentry->d_flags, flags); WRITE_ONCE(dentry->d_flags, flags);
dentry->d_inode = NULL; dentry->d_inode = NULL;
if (dentry->d_flags & DCACHE_LRU_LIST)
this_cpu_inc(nr_dentry_negative);
} }
static void dentry_free(struct dentry *dentry) static void dentry_free(struct dentry *dentry)
@ -371,6 +385,11 @@ static void dentry_unlink_inode(struct dentry * dentry)
* The per-cpu "nr_dentry_unused" counters are updated with * The per-cpu "nr_dentry_unused" counters are updated with
* the DCACHE_LRU_LIST bit. * the DCACHE_LRU_LIST bit.
* *
* The per-cpu "nr_dentry_negative" counters are only updated
* when deleted from or added to the per-superblock LRU list, not
* from/to the shrink list. That is to avoid an unneeded dec/inc
* pair when moving from LRU to shrink list in select_collect().
*
* These helper functions make sure we always follow the * These helper functions make sure we always follow the
* rules. d_lock must be held by the caller. * rules. d_lock must be held by the caller.
*/ */
@ -380,6 +399,8 @@ static void d_lru_add(struct dentry *dentry)
D_FLAG_VERIFY(dentry, 0); D_FLAG_VERIFY(dentry, 0);
dentry->d_flags |= DCACHE_LRU_LIST; dentry->d_flags |= DCACHE_LRU_LIST;
this_cpu_inc(nr_dentry_unused); this_cpu_inc(nr_dentry_unused);
if (d_is_negative(dentry))
this_cpu_inc(nr_dentry_negative);
WARN_ON_ONCE(!list_lru_add(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)); WARN_ON_ONCE(!list_lru_add(&dentry->d_sb->s_dentry_lru, &dentry->d_lru));
} }
@ -388,6 +409,8 @@ static void d_lru_del(struct dentry *dentry)
D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
dentry->d_flags &= ~DCACHE_LRU_LIST; dentry->d_flags &= ~DCACHE_LRU_LIST;
this_cpu_dec(nr_dentry_unused); this_cpu_dec(nr_dentry_unused);
if (d_is_negative(dentry))
this_cpu_dec(nr_dentry_negative);
WARN_ON_ONCE(!list_lru_del(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)); WARN_ON_ONCE(!list_lru_del(&dentry->d_sb->s_dentry_lru, &dentry->d_lru));
} }
@ -418,6 +441,8 @@ static void d_lru_isolate(struct list_lru_one *lru, struct dentry *dentry)
D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
dentry->d_flags &= ~DCACHE_LRU_LIST; dentry->d_flags &= ~DCACHE_LRU_LIST;
this_cpu_dec(nr_dentry_unused); this_cpu_dec(nr_dentry_unused);
if (d_is_negative(dentry))
this_cpu_dec(nr_dentry_negative);
list_lru_isolate(lru, &dentry->d_lru); list_lru_isolate(lru, &dentry->d_lru);
} }
@ -426,6 +451,8 @@ static void d_lru_shrink_move(struct list_lru_one *lru, struct dentry *dentry,
{ {
D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
dentry->d_flags |= DCACHE_SHRINK_LIST; dentry->d_flags |= DCACHE_SHRINK_LIST;
if (d_is_negative(dentry))
this_cpu_dec(nr_dentry_negative);
list_lru_isolate_move(lru, &dentry->d_lru, list); list_lru_isolate_move(lru, &dentry->d_lru, list);
} }
@ -1816,6 +1843,11 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)
WARN_ON(d_in_lookup(dentry)); WARN_ON(d_in_lookup(dentry));
spin_lock(&dentry->d_lock); spin_lock(&dentry->d_lock);
/*
* Decrement negative dentry count if it was in the LRU list.
*/
if (dentry->d_flags & DCACHE_LRU_LIST)
this_cpu_dec(nr_dentry_negative);
hlist_add_head(&dentry->d_u.d_alias, &inode->i_dentry); hlist_add_head(&dentry->d_u.d_alias, &inode->i_dentry);
raw_write_seqcount_begin(&dentry->d_seq); raw_write_seqcount_begin(&dentry->d_seq);
__d_set_inode_and_type(dentry, inode, add_flags); __d_set_inode_and_type(dentry, inode, add_flags);

View File

@ -62,9 +62,10 @@ extern const struct qstr slash_name;
struct dentry_stat_t { struct dentry_stat_t {
long nr_dentry; long nr_dentry;
long nr_unused; long nr_unused;
long age_limit; /* age in seconds */ long age_limit; /* age in seconds */
long want_pages; /* pages requested by system */ long want_pages; /* pages requested by system */
long dummy[2]; long nr_negative; /* # of unused negative dentries */
long dummy; /* Reserved for future use */
}; };
extern struct dentry_stat_t dentry_stat; extern struct dentry_stat_t dentry_stat;