Currently /sys/kernel/debug/bdi/$maj:$min/stats shows only root bdi wb.
With CONFIG_CGROUP_WRITEBACK=y there is one for each memory cgroup.

This patch shows here state of each bdi_writeback in form:

<global state>

Id: 1
Cgroup: /
<root wb state>

Id: xxx
Cgroup: /path
<cgroup wb state>

Id: yyy
Cgroup: /path2
<cgroup wb state>

...

Signed-off-by: Konstantin Khlebnikov <[email protected]>
---
 mm/backing-dev.c |  106 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 93 insertions(+), 13 deletions(-)

diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index e8e89158adec..3e752c4bafaf 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -45,7 +45,7 @@ static void bdi_debug_init(void)
 static int bdi_debug_stats_show(struct seq_file *m, void *v)
 {
        struct backing_dev_info *bdi = m->private;
-       struct bdi_writeback *wb = &bdi->wb;
+       struct bdi_writeback *wb = v;
        unsigned long background_thresh;
        unsigned long dirty_thresh;
        unsigned long wb_thresh;
@@ -65,43 +65,123 @@ static int bdi_debug_stats_show(struct seq_file *m, void 
*v)
                        nr_dirty_time++;
        spin_unlock(&wb->list_lock);
 
-       global_dirty_limits(&background_thresh, &dirty_thresh);
-       wb_thresh = wb_calc_thresh(wb, dirty_thresh);
-
 #define K(x) ((x) << (PAGE_SHIFT - 10))
+
+       /* global state */
+       if (wb == &bdi->wb) {
+               global_dirty_limits(&background_thresh, &dirty_thresh);
+               wb_thresh = wb_calc_thresh(wb, dirty_thresh);
+               seq_printf(m,
+                          "BdiDirtyThresh:     %10lu kB\n"
+                          "DirtyThresh:        %10lu kB\n"
+                          "BackgroundThresh:   %10lu kB\n"
+                          "bdi_list:           %10u\n",
+                          K(wb_thresh),
+                          K(dirty_thresh),
+                          K(background_thresh),
+                          !list_empty(&bdi->bdi_list));
+       }
+
+       /* cgroup header */
+#ifdef CONFIG_CGROUP_WRITEBACK
+       if (bdi->capabilities & BDI_CAP_CGROUP_WRITEBACK) {
+               size_t buflen, len;
+               char *buf;
+
+               seq_printf(m, "\nId: %d\nCgroup: ", wb->memcg_css->id);
+               buflen = seq_get_buf(m, &buf);
+               if (buf) {
+                       len = cgroup_path(wb->memcg_css->cgroup, buf, buflen);
+                       seq_commit(m, len <= buflen ? len : -1);
+                       seq_putc(m, '\n');
+               }
+       }
+#endif /* CONFIG_CGROUP_WRITEBACK */
+
        seq_printf(m,
                   "BdiWriteback:       %10lu kB\n"
                   "BdiReclaimable:     %10lu kB\n"
-                  "BdiDirtyThresh:     %10lu kB\n"
-                  "DirtyThresh:        %10lu kB\n"
-                  "BackgroundThresh:   %10lu kB\n"
                   "BdiDirtied:         %10lu kB\n"
                   "BdiWritten:         %10lu kB\n"
                   "BdiWriteBandwidth:  %10lu kBps\n"
+                  "BdiAvgWriteBwidth:  %10lu kBps\n"
                   "b_dirty:            %10lu\n"
                   "b_io:               %10lu\n"
                   "b_more_io:          %10lu\n"
                   "b_dirty_time:       %10lu\n"
-                  "bdi_list:           %10u\n"
                   "state:              %10lx\n",
                   (unsigned long) K(wb_stat(wb, WB_WRITEBACK)),
                   (unsigned long) K(wb_stat(wb, WB_RECLAIMABLE)),
-                  K(wb_thresh),
-                  K(dirty_thresh),
-                  K(background_thresh),
                   (unsigned long) K(wb_stat(wb, WB_DIRTIED)),
                   (unsigned long) K(wb_stat(wb, WB_WRITTEN)),
                   (unsigned long) K(wb->write_bandwidth),
+                  (unsigned long) K(wb->avg_write_bandwidth),
                   nr_dirty,
                   nr_io,
                   nr_more_io,
                   nr_dirty_time,
-                  !list_empty(&bdi->bdi_list), bdi->wb.state);
+                  wb->state);
 #undef K
 
        return 0;
 }
-DEFINE_SHOW_ATTRIBUTE(bdi_debug_stats);
+
+static void *bdi_debug_stats_start(struct seq_file *m, loff_t *ppos)
+{
+       struct backing_dev_info *bdi = m->private;
+       struct bdi_writeback *wb;
+       loff_t pos = *ppos;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node)
+               if (pos-- == 0)
+                       return wb;
+       return NULL;
+}
+
+static void *bdi_debug_stats_next(struct seq_file *m, void *v, loff_t *ppos)
+{
+       struct backing_dev_info *bdi = m->private;
+       struct bdi_writeback *wb = v;
+
+       list_for_each_entry_continue_rcu(wb, &bdi->wb_list, bdi_node) {
+               ++*ppos;
+               return wb;
+       }
+       return NULL;
+}
+
+static void bdi_debug_stats_stop(struct seq_file *m, void *v)
+{
+       rcu_read_unlock();
+}
+
+static const struct seq_operations bdi_debug_stats_seq_ops = {
+       .start  = bdi_debug_stats_start,
+       .next   = bdi_debug_stats_next,
+       .stop   = bdi_debug_stats_stop,
+       .show   = bdi_debug_stats_show,
+};
+
+static int bdi_debug_stats_open(struct inode *inode, struct file *file)
+{
+       struct seq_file *m;
+       int ret;
+
+       ret = seq_open(file, &bdi_debug_stats_seq_ops);
+       if (!ret) {
+               m = file->private_data;
+               m->private = inode->i_private;
+       }
+       return ret;
+}
+
+static const struct file_operations bdi_debug_stats_fops = {
+       .open           = bdi_debug_stats_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
 
 static void bdi_debug_register(struct backing_dev_info *bdi, const char *name)
 {

Reply via email to