Message ID | 1536693506-11949-4-git-send-email-longman@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fs/dcache: Track # of negative dentries | expand |
On Tue, Sep 11, 2018 at 03:18:25PM -0400, Waiman Long wrote: > 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 7th field in the Not the 7th field anymore. > /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. > > 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> .... > --- > Documentation/sysctl/fs.txt | 26 ++++++++++++++++---------- > fs/dcache.c | 31 +++++++++++++++++++++++++++++++ > include/linux/dcache.h | 7 ++++--- > 3 files changed, 51 insertions(+), 13 deletions(-) > > diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt > index 819caf8..3b4f441 100644 > --- a/Documentation/sysctl/fs.txt > +++ b/Documentation/sysctl/fs.txt > @@ -56,26 +56,32 @@ of any kernel data structures. > > 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_unused; > int age_limit; /* age in seconds */ > int want_pages; /* pages requested by system */ > - int dummy[2]; > -} dentry_stat = {0, 0, 45, 0,}; > --------------------------------------------------------------- > - > -Dentries are dynamically allocated and deallocated, and > -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 > -used. Nr_unused seems to be exactly what its name says. > + int nr_negative; /* # of unused negative dentries */ > + int dummy; /* Reserved */ /* reserved for future use */ .... > @@ -331,6 +343,8 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) > flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU); > WRITE_ONCE(dentry->d_flags, flags); > dentry->d_inode = NULL; > + if (dentry->d_flags & DCACHE_LRU_LIST) > + this_cpu_inc(nr_dentry_negative); > } > > static void dentry_free(struct dentry *dentry) > @@ -385,6 +399,10 @@ static void dentry_unlink_inode(struct dentry * dentry) > * The per-cpu "nr_dentry_unused" counters are updated with > * the DCACHE_LRU_LIST bit. > * > + * The per-cpu "nr_dentry_negative" counters are only updated > + * when deleted or added to the per-superblock LRU list, not > + * on the shrink list. This tells us what the code is doing, but it doesn't explain why a different accounting method to nr_dentry_unused was chosen. What constraints require the accounting to be done this way rather than just mirror the unused dentry accounting? > @@ -1836,6 +1862,11 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode) > WARN_ON(d_in_lookup(dentry)); > > 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); > raw_write_seqcount_begin(&dentry->d_seq); > __d_set_inode_and_type(dentry, inode, add_flags); > diff --git a/include/linux/dcache.h b/include/linux/dcache.h > index ef4b70f..73ff9f0 100644 > --- a/include/linux/dcache.h > +++ b/include/linux/dcache.h > @@ -62,9 +62,10 @@ struct qstr { > struct dentry_stat_t { > long nr_dentry; > long nr_unused; > - long age_limit; /* age in seconds */ > - long want_pages; /* pages requested by system */ > - long dummy[2]; > + long age_limit; /* age in seconds */ > + long want_pages; /* pages requested by system */ > + long nr_negative; /* # of unused negative dentries */ > + long dummy; /* Reserved */ /* reserved for future use */ Cheers, Dave.
On 09/11/2018 06:08 PM, Dave Chinner wrote: > On Tue, Sep 11, 2018 at 03:18:25PM -0400, Waiman Long wrote: >> 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 7th field in the > Not the 7th field anymore. > You are right. It is a left-behind from v2. >> /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. >> >> 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> > .... >> --- >> Documentation/sysctl/fs.txt | 26 ++++++++++++++++---------- >> fs/dcache.c | 31 +++++++++++++++++++++++++++++++ >> include/linux/dcache.h | 7 ++++--- >> 3 files changed, 51 insertions(+), 13 deletions(-) >> >> diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt >> index 819caf8..3b4f441 100644 >> --- a/Documentation/sysctl/fs.txt >> +++ b/Documentation/sysctl/fs.txt >> @@ -56,26 +56,32 @@ of any kernel data structures. >> >> 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_unused; >> int age_limit; /* age in seconds */ >> int want_pages; /* pages requested by system */ >> - int dummy[2]; >> -} dentry_stat = {0, 0, 45, 0,}; >> --------------------------------------------------------------- >> - >> -Dentries are dynamically allocated and deallocated, and >> -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 >> -used. Nr_unused seems to be exactly what its name says. >> + int nr_negative; /* # of unused negative dentries */ >> + int dummy; /* Reserved */ > /* reserved for future use */ Will change that. > .... >> @@ -331,6 +343,8 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) >> flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU); >> WRITE_ONCE(dentry->d_flags, flags); >> dentry->d_inode = NULL; >> + if (dentry->d_flags & DCACHE_LRU_LIST) >> + this_cpu_inc(nr_dentry_negative); >> } >> >> static void dentry_free(struct dentry *dentry) >> @@ -385,6 +399,10 @@ static void dentry_unlink_inode(struct dentry * dentry) >> * The per-cpu "nr_dentry_unused" counters are updated with >> * the DCACHE_LRU_LIST bit. >> * >> + * The per-cpu "nr_dentry_negative" counters are only updated >> + * when deleted or added to the per-superblock LRU list, not >> + * on the shrink list. > This tells us what the code is doing, but it doesn't explain why > a different accounting method to nr_dentry_unused was chosen. What > constraints require the accounting to be done this way rather than > just mirror the unused dentry accounting? It is done to minimize the number of percpu count update as much as possible. There is one code path where the unused count is decremented when removing from the lru and then increment later on when added to the shrink list. So we are doing double inc/dec in this case. Besides, those in the shrink list are on the way out and its number isn't really that important. I will elaborate a bit more on the rationale behind this decision in the patch. >> @@ -1836,6 +1862,11 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode) >> WARN_ON(d_in_lookup(dentry)); >> >> 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); >> raw_write_seqcount_begin(&dentry->d_seq); >> __d_set_inode_and_type(dentry, inode, add_flags); >> diff --git a/include/linux/dcache.h b/include/linux/dcache.h >> index ef4b70f..73ff9f0 100644 >> --- a/include/linux/dcache.h >> +++ b/include/linux/dcache.h >> @@ -62,9 +62,10 @@ struct qstr { >> struct dentry_stat_t { >> long nr_dentry; >> long nr_unused; >> - long age_limit; /* age in seconds */ >> - long want_pages; /* pages requested by system */ >> - long dummy[2]; >> + long age_limit; /* age in seconds */ >> + long want_pages; /* pages requested by system */ >> + long nr_negative; /* # of unused negative dentries */ >> + long dummy; /* Reserved */ > /* reserved for future use */ Will do. Cheers, Longman
diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt index 819caf8..3b4f441 100644 --- a/Documentation/sysctl/fs.txt +++ b/Documentation/sysctl/fs.txt @@ -56,26 +56,32 @@ of any kernel data structures. 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_unused; int age_limit; /* age in seconds */ int want_pages; /* pages requested by system */ - int dummy[2]; -} dentry_stat = {0, 0, 45, 0,}; --------------------------------------------------------------- - -Dentries are dynamically allocated and deallocated, and -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 -used. Nr_unused seems to be exactly what its name says. + int nr_negative; /* # of unused negative dentries */ + int dummy; /* Reserved */ +}; +-------------------------------------------------------------- + +Dentries are dynamically allocated and deallocated. + +nr_dentry shows the total number of dentries allocated (active ++ 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 can be reclaimed when memory is short and want_pages is nonzero when shrink_dcache_pages() has been called and the 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: diff --git a/fs/dcache.c b/fs/dcache.c index cb515f1..c1cc956 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -119,6 +119,7 @@ struct dentry_stat_t dentry_stat = { static DEFINE_PER_CPU(long, nr_dentry); static DEFINE_PER_CPU(long, nr_dentry_unused); +static DEFINE_PER_CPU(long, nr_dentry_negative); #if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS) @@ -152,11 +153,22 @@ static long get_nr_dentry_unused(void) 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, size_t *lenp, loff_t *ppos) { dentry_stat.nr_dentry = get_nr_dentry(); 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); } #endif @@ -331,6 +343,8 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU); WRITE_ONCE(dentry->d_flags, flags); dentry->d_inode = NULL; + if (dentry->d_flags & DCACHE_LRU_LIST) + this_cpu_inc(nr_dentry_negative); } static void dentry_free(struct dentry *dentry) @@ -385,6 +399,10 @@ static void dentry_unlink_inode(struct dentry * dentry) * The per-cpu "nr_dentry_unused" counters are updated with * the DCACHE_LRU_LIST bit. * + * The per-cpu "nr_dentry_negative" counters are only updated + * when deleted or added to the per-superblock LRU list, not + * on the shrink list. + * * These helper functions make sure we always follow the * rules. d_lock must be held by the caller. */ @@ -394,6 +412,8 @@ static void d_lru_add(struct dentry *dentry) D_FLAG_VERIFY(dentry, 0); dentry->d_flags |= DCACHE_LRU_LIST; 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)); } @@ -402,6 +422,8 @@ static void d_lru_del(struct dentry *dentry) D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); dentry->d_flags &= ~DCACHE_LRU_LIST; 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)); } @@ -432,6 +454,8 @@ static void d_lru_isolate(struct list_lru_one *lru, struct dentry *dentry) D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); dentry->d_flags &= ~DCACHE_LRU_LIST; this_cpu_dec(nr_dentry_unused); + if (d_is_negative(dentry)) + this_cpu_dec(nr_dentry_negative); list_lru_isolate(lru, &dentry->d_lru); } @@ -440,6 +464,8 @@ static void d_lru_shrink_move(struct list_lru_one *lru, struct dentry *dentry, { D_FLAG_VERIFY(dentry, DCACHE_LRU_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); } @@ -1836,6 +1862,11 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode) WARN_ON(d_in_lookup(dentry)); 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); raw_write_seqcount_begin(&dentry->d_seq); __d_set_inode_and_type(dentry, inode, add_flags); diff --git a/include/linux/dcache.h b/include/linux/dcache.h index ef4b70f..73ff9f0 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -62,9 +62,10 @@ struct qstr { struct dentry_stat_t { long nr_dentry; long nr_unused; - long age_limit; /* age in seconds */ - long want_pages; /* pages requested by system */ - long dummy[2]; + long age_limit; /* age in seconds */ + long want_pages; /* pages requested by system */ + long nr_negative; /* # of unused negative dentries */ + long dummy; /* Reserved */ }; extern struct dentry_stat_t dentry_stat;
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 7th 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. 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> --- Documentation/sysctl/fs.txt | 26 ++++++++++++++++---------- fs/dcache.c | 31 +++++++++++++++++++++++++++++++ include/linux/dcache.h | 7 ++++--- 3 files changed, 51 insertions(+), 13 deletions(-)