diff mbox series

[v2,3/7] mm: introduce memcg interfaces for shrinker debugfs

Message ID 20220422202644.799732-4-roman.gushchin@linux.dev (mailing list archive)
State New
Headers show
Series mm: introduce shrinker debugfs interface | expand

Commit Message

Roman Gushchin April 22, 2022, 8:26 p.m. UTC
This commit introduces "count_memcg" and "scan_memcg" interfaces
for memcg-aware shrinkers.

Count_memcg using the following format:
<cgroup inode number1> <count2>
<cgroup inode number2> <count2>
...

Memory cgroups with 0 associated objects are skipped.

Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
---
 mm/shrinker_debug.c | 186 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 139 insertions(+), 47 deletions(-)

Comments

Hillf Danton April 23, 2022, 12:35 a.m. UTC | #1
On Fri, 22 Apr 2022 13:26:40 -0700 Roman Gushchin wrote:
> This commit introduces "count_memcg" and "scan_memcg" interfaces
> for memcg-aware shrinkers.
> 
> Count_memcg using the following format:
> <cgroup inode number1> <count2>
> <cgroup inode number2> <count2>
> ...
> 
> Memory cgroups with 0 associated objects are skipped.
> 
> Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
> ---
>  mm/shrinker_debug.c | 186 +++++++++++++++++++++++++++++++++-----------
>  1 file changed, 139 insertions(+), 47 deletions(-)
> 
> diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
> index 4df7382a0737..002d44d6ad56 100644
> --- a/mm/shrinker_debug.c
> +++ b/mm/shrinker_debug.c
> @@ -1,8 +1,10 @@
>  // SPDX-License-Identifier: GPL-2.0
>  #include <linux/idr.h>
> +#include <linux/slab.h>
>  #include <linux/debugfs.h>
>  #include <linux/seq_file.h>
>  #include <linux/shrinker.h>
> +#include <linux/memcontrol.h>
>  
>  /* defined in vmscan.c */
>  extern struct rw_semaphore shrinker_rwsem;
> @@ -11,25 +13,25 @@ extern struct list_head shrinker_list;
>  static DEFINE_IDA(shrinker_debugfs_ida);
>  static struct dentry *shrinker_debugfs_root;
>  
> -static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
> +static unsigned long shrinker_count_objects(struct shrinker *shrinker,
> +					    struct mem_cgroup *memcg,
> +					    unsigned long *count_per_node)
>  {
> -	struct shrinker *shrinker = (struct shrinker *)m->private;
>  	unsigned long nr, total = 0;
> -	int ret, nid;
> -
> -	ret = down_read_killable(&shrinker_rwsem);
> -	if (ret)
> -		return ret;
> +	int nid;
>  
>  	for_each_node(nid) {
>  		struct shrink_control sc = {
>  			.gfp_mask = GFP_KERNEL,
>  			.nid = nid,
> +			.memcg = memcg,
>  		};
>  
>  		nr = shrinker->count_objects(shrinker, &sc);
>  		if (nr == SHRINK_EMPTY)
>  			nr = 0;
> +		if (count_per_node)
> +			count_per_node[nid] = nr;
>  		total += nr;
>  
>  		if (!(shrinker->flags & SHRINKER_NUMA_AWARE))
> @@ -37,32 +39,17 @@ static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
>  
>  		cond_resched();

Nit, add a line in response to signal before schedule, given the
killable above.

>  	}
> -	up_read(&shrinker_rwsem);
> -
> -	seq_printf(m, "%lu\n", total);
>  
> -	return ret;
> +	return total;
>  }
> -DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
>  
> -static ssize_t shrinker_debugfs_scan_write(struct file *file,
> -					   const char __user *buf,
> -					   size_t size, loff_t *pos)
> +static int shrinker_scan_objects(struct shrinker *shrinker,
> +				 struct mem_cgroup *memcg,
> +				 unsigned long nr_to_scan)
>  {
> -	struct shrinker *shrinker = (struct shrinker *)file->private_data;
> -	unsigned long nr, total = 0, nr_to_scan;
> -	unsigned long *count_per_node = NULL;
> -	int nid;
> -	char kbuf[24];
> -	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
> -	ssize_t ret;
> -
> -	if (copy_from_user(kbuf, buf, read_len))
> -		return -EFAULT;
> -	kbuf[read_len] = '\0';
> -
> -	if (kstrtoul(kbuf, 10, &nr_to_scan))
> -		return -EINVAL;
> +	unsigned long *count_per_node;
> +	unsigned long total, nr;
> +	int ret, nid;
>  
>  	ret = down_read_killable(&shrinker_rwsem);
>  	if (ret)
> @@ -80,20 +67,7 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
>  			goto out;
>  		}
>  
> -		for_each_node(nid) {
> -			struct shrink_control sc = {
> -				.gfp_mask = GFP_KERNEL,
> -				.nid = nid,
> -			};
> -
> -			nr = shrinker->count_objects(shrinker, &sc);
> -			if (nr == SHRINK_EMPTY)
> -				nr = 0;
> -			count_per_node[nid] = nr;
> -			total += nr;
> -
> -			cond_resched();
> -		}
> +		total = shrinker_count_objects(shrinker, memcg, count_per_node);
>  	}
>  
>  	for_each_node(nid) {
> @@ -102,13 +76,13 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
>  			.nid = nid,
>  		};
>  
> -		if (shrinker->flags & SHRINKER_NUMA_AWARE) {
> +		if (count_per_node) {
>  			sc.nr_to_scan = nr_to_scan * count_per_node[nid] /
>  				(total ? total : 1);
>  			sc.nr_scanned = sc.nr_to_scan;
>  		} else {
>  			sc.nr_to_scan = nr_to_scan;
> -			sc.nr_scanned = sc.nr_to_scan;
> +			sc.nr_scanned = nr_to_scan;
>  		}
>  
>  		nr = shrinker->scan_objects(shrinker, &sc);
> @@ -119,15 +93,51 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
>  			break;
>  
>  		cond_resched();
> -
>  	}
> -	ret = size;
>  out:
>  	up_read(&shrinker_rwsem);
>  	kfree(count_per_node);
>  	return ret;
>  }
>  
> +static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
> +{
> +	struct shrinker *shrinker = (struct shrinker *)m->private;
> +	int ret;
> +
> +	ret = down_read_killable(&shrinker_rwsem);
> +	if (!ret) {
> +		unsigned long total = shrinker_count_objects(shrinker, NULL, NULL);
> +
> +		up_read(&shrinker_rwsem);
> +		seq_printf(m, "%lu\n", total);
> +	}
> +	return ret;
> +}
> +DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
> +
> +static ssize_t shrinker_debugfs_scan_write(struct file *file,
> +					   const char __user *buf,
> +					   size_t size, loff_t *pos)
> +{
> +	struct shrinker *shrinker = (struct shrinker *)file->private_data;
> +	unsigned long nr_to_scan;
> +	char kbuf[24];
> +	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
> +	ssize_t ret;
> +
> +	if (copy_from_user(kbuf, buf, read_len))
> +		return -EFAULT;
> +	kbuf[read_len] = '\0';
> +
> +	if (kstrtoul(kbuf, 10, &nr_to_scan))
> +		return -EINVAL;
> +
> +	ret = shrinker_scan_objects(shrinker, NULL, nr_to_scan);
> +
> +	return ret ? ret : size;
> +}
> +
>  static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
>  {
>  	file->private_data = inode->i_private;
> @@ -140,6 +150,78 @@ static const struct file_operations shrinker_debugfs_scan_fops = {
>  	.write	 = shrinker_debugfs_scan_write,
>  };
>  
> +#ifdef CONFIG_MEMCG
> +static int shrinker_debugfs_count_memcg_show(struct seq_file *m, void *v)
> +{
> +	struct shrinker *shrinker = (struct shrinker *)m->private;
> +	struct mem_cgroup *memcg;
> +	unsigned long total;
> +	int ret;
> +
> +	ret = down_read_killable(&shrinker_rwsem);
> +	if (ret)
> +		return ret;
> +	rcu_read_lock();

A minute ... things like cond_resched() or mutex in individual shrinker
implementation can ruin your nice work within two seconds. The bigger
pain is can we rule them out from coming shrinkers?

Hillf
> +
> +	memcg = mem_cgroup_iter(NULL, NULL, NULL);
> +	do {

Nit, take a look at pending signal.

> +		if (!mem_cgroup_online(memcg))
> +			continue;
> +
> +		total = shrinker_count_objects(shrinker, memcg, NULL);
> +		if (!total)
> +			continue;
> +
> +		seq_printf(m, "%lu %lu\n", mem_cgroup_ino(memcg), total);
> +	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
> +
> +	rcu_read_unlock();
> +	up_read(&shrinker_rwsem);
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count_memcg);
Roman Gushchin April 23, 2022, 1:29 a.m. UTC | #2
On Sat, Apr 23, 2022 at 08:35:52AM +0800, Hillf Danton wrote:
> On Fri, 22 Apr 2022 13:26:40 -0700 Roman Gushchin wrote:
> > This commit introduces "count_memcg" and "scan_memcg" interfaces
> > for memcg-aware shrinkers.
> > 
> > Count_memcg using the following format:
> > <cgroup inode number1> <count2>
> > <cgroup inode number2> <count2>
> > ...
> > 
> > Memory cgroups with 0 associated objects are skipped.
> > 
> > Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
> > ---
> >  mm/shrinker_debug.c | 186 +++++++++++++++++++++++++++++++++-----------
> >  1 file changed, 139 insertions(+), 47 deletions(-)
> > 
> > diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
> > index 4df7382a0737..002d44d6ad56 100644
> > --- a/mm/shrinker_debug.c
> > +++ b/mm/shrinker_debug.c
> > @@ -1,8 +1,10 @@
> >  // SPDX-License-Identifier: GPL-2.0
> >  #include <linux/idr.h>
> > +#include <linux/slab.h>
> >  #include <linux/debugfs.h>
> >  #include <linux/seq_file.h>
> >  #include <linux/shrinker.h>
> > +#include <linux/memcontrol.h>
> >  
> >  /* defined in vmscan.c */
> >  extern struct rw_semaphore shrinker_rwsem;
> > @@ -11,25 +13,25 @@ extern struct list_head shrinker_list;
> >  static DEFINE_IDA(shrinker_debugfs_ida);
> >  static struct dentry *shrinker_debugfs_root;
> >  
> > -static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
> > +static unsigned long shrinker_count_objects(struct shrinker *shrinker,
> > +					    struct mem_cgroup *memcg,
> > +					    unsigned long *count_per_node)
> >  {
> > -	struct shrinker *shrinker = (struct shrinker *)m->private;
> >  	unsigned long nr, total = 0;
> > -	int ret, nid;
> > -
> > -	ret = down_read_killable(&shrinker_rwsem);
> > -	if (ret)
> > -		return ret;
> > +	int nid;
> >  
> >  	for_each_node(nid) {
> >  		struct shrink_control sc = {
> >  			.gfp_mask = GFP_KERNEL,
> >  			.nid = nid,
> > +			.memcg = memcg,
> >  		};
> >  
> >  		nr = shrinker->count_objects(shrinker, &sc);
> >  		if (nr == SHRINK_EMPTY)
> >  			nr = 0;
> > +		if (count_per_node)
> > +			count_per_node[nid] = nr;
> >  		total += nr;
> >  
> >  		if (!(shrinker->flags & SHRINKER_NUMA_AWARE))
> > @@ -37,32 +39,17 @@ static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
> >  
> >  		cond_resched();
> 
> Nit, add a line in response to signal before schedule, given the
> killable above.

Good point, thanks!

> 
> >  	}
> > -	up_read(&shrinker_rwsem);
> > -
> > -	seq_printf(m, "%lu\n", total);
> >  
> > -	return ret;
> > +	return total;
> >  }
> > -DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
> >  
> > -static ssize_t shrinker_debugfs_scan_write(struct file *file,
> > -					   const char __user *buf,
> > -					   size_t size, loff_t *pos)
> > +static int shrinker_scan_objects(struct shrinker *shrinker,
> > +				 struct mem_cgroup *memcg,
> > +				 unsigned long nr_to_scan)
> >  {
> > -	struct shrinker *shrinker = (struct shrinker *)file->private_data;
> > -	unsigned long nr, total = 0, nr_to_scan;
> > -	unsigned long *count_per_node = NULL;
> > -	int nid;
> > -	char kbuf[24];
> > -	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
> > -	ssize_t ret;
> > -
> > -	if (copy_from_user(kbuf, buf, read_len))
> > -		return -EFAULT;
> > -	kbuf[read_len] = '\0';
> > -
> > -	if (kstrtoul(kbuf, 10, &nr_to_scan))
> > -		return -EINVAL;
> > +	unsigned long *count_per_node;
> > +	unsigned long total, nr;
> > +	int ret, nid;
> >  
> >  	ret = down_read_killable(&shrinker_rwsem);
> >  	if (ret)
> > @@ -80,20 +67,7 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
> >  			goto out;
> >  		}
> >  
> > -		for_each_node(nid) {
> > -			struct shrink_control sc = {
> > -				.gfp_mask = GFP_KERNEL,
> > -				.nid = nid,
> > -			};
> > -
> > -			nr = shrinker->count_objects(shrinker, &sc);
> > -			if (nr == SHRINK_EMPTY)
> > -				nr = 0;
> > -			count_per_node[nid] = nr;
> > -			total += nr;
> > -
> > -			cond_resched();
> > -		}
> > +		total = shrinker_count_objects(shrinker, memcg, count_per_node);
> >  	}
> >  
> >  	for_each_node(nid) {
> > @@ -102,13 +76,13 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
> >  			.nid = nid,
> >  		};
> >  
> > -		if (shrinker->flags & SHRINKER_NUMA_AWARE) {
> > +		if (count_per_node) {
> >  			sc.nr_to_scan = nr_to_scan * count_per_node[nid] /
> >  				(total ? total : 1);
> >  			sc.nr_scanned = sc.nr_to_scan;
> >  		} else {
> >  			sc.nr_to_scan = nr_to_scan;
> > -			sc.nr_scanned = sc.nr_to_scan;
> > +			sc.nr_scanned = nr_to_scan;
> >  		}
> >  
> >  		nr = shrinker->scan_objects(shrinker, &sc);
> > @@ -119,15 +93,51 @@ static ssize_t shrinker_debugfs_scan_write(struct file *file,
> >  			break;
> >  
> >  		cond_resched();
> > -
> >  	}
> > -	ret = size;
> >  out:
> >  	up_read(&shrinker_rwsem);
> >  	kfree(count_per_node);
> >  	return ret;
> >  }
> >  
> > +static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
> > +{
> > +	struct shrinker *shrinker = (struct shrinker *)m->private;
> > +	int ret;
> > +
> > +	ret = down_read_killable(&shrinker_rwsem);
> > +	if (!ret) {
> > +		unsigned long total = shrinker_count_objects(shrinker, NULL, NULL);
> > +
> > +		up_read(&shrinker_rwsem);
> > +		seq_printf(m, "%lu\n", total);
> > +	}
> > +	return ret;
> > +}
> > +DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
> > +
> > +static ssize_t shrinker_debugfs_scan_write(struct file *file,
> > +					   const char __user *buf,
> > +					   size_t size, loff_t *pos)
> > +{
> > +	struct shrinker *shrinker = (struct shrinker *)file->private_data;
> > +	unsigned long nr_to_scan;
> > +	char kbuf[24];
> > +	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
> > +	ssize_t ret;
> > +
> > +	if (copy_from_user(kbuf, buf, read_len))
> > +		return -EFAULT;
> > +	kbuf[read_len] = '\0';
> > +
> > +	if (kstrtoul(kbuf, 10, &nr_to_scan))
> > +		return -EINVAL;
> > +
> > +	ret = shrinker_scan_objects(shrinker, NULL, nr_to_scan);
> > +
> > +	return ret ? ret : size;
> > +}
> > +
> >  static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
> >  {
> >  	file->private_data = inode->i_private;
> > @@ -140,6 +150,78 @@ static const struct file_operations shrinker_debugfs_scan_fops = {
> >  	.write	 = shrinker_debugfs_scan_write,
> >  };
> >  
> > +#ifdef CONFIG_MEMCG
> > +static int shrinker_debugfs_count_memcg_show(struct seq_file *m, void *v)
> > +{
> > +	struct shrinker *shrinker = (struct shrinker *)m->private;
> > +	struct mem_cgroup *memcg;
> > +	unsigned long total;
> > +	int ret;
> > +
> > +	ret = down_read_killable(&shrinker_rwsem);
> > +	if (ret)
> > +		return ret;
> > +	rcu_read_lock();
> 
> A minute ... things like cond_resched() or mutex in individual shrinker
> implementation can ruin your nice work within two seconds. The bigger
> pain is can we rule them out from coming shrinkers?

Hm, why? Isn't it the same path as in reclaim? Can you, please, elaborate?
diff mbox series

Patch

diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index 4df7382a0737..002d44d6ad56 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -1,8 +1,10 @@ 
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/idr.h>
+#include <linux/slab.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
+#include <linux/memcontrol.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -11,25 +13,25 @@  extern struct list_head shrinker_list;
 static DEFINE_IDA(shrinker_debugfs_ida);
 static struct dentry *shrinker_debugfs_root;
 
-static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
+static unsigned long shrinker_count_objects(struct shrinker *shrinker,
+					    struct mem_cgroup *memcg,
+					    unsigned long *count_per_node)
 {
-	struct shrinker *shrinker = (struct shrinker *)m->private;
 	unsigned long nr, total = 0;
-	int ret, nid;
-
-	ret = down_read_killable(&shrinker_rwsem);
-	if (ret)
-		return ret;
+	int nid;
 
 	for_each_node(nid) {
 		struct shrink_control sc = {
 			.gfp_mask = GFP_KERNEL,
 			.nid = nid,
+			.memcg = memcg,
 		};
 
 		nr = shrinker->count_objects(shrinker, &sc);
 		if (nr == SHRINK_EMPTY)
 			nr = 0;
+		if (count_per_node)
+			count_per_node[nid] = nr;
 		total += nr;
 
 		if (!(shrinker->flags & SHRINKER_NUMA_AWARE))
@@ -37,32 +39,17 @@  static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
 
 		cond_resched();
 	}
-	up_read(&shrinker_rwsem);
-
-	seq_printf(m, "%lu\n", total);
 
-	return ret;
+	return total;
 }
-DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
 
-static ssize_t shrinker_debugfs_scan_write(struct file *file,
-					   const char __user *buf,
-					   size_t size, loff_t *pos)
+static int shrinker_scan_objects(struct shrinker *shrinker,
+				 struct mem_cgroup *memcg,
+				 unsigned long nr_to_scan)
 {
-	struct shrinker *shrinker = (struct shrinker *)file->private_data;
-	unsigned long nr, total = 0, nr_to_scan;
-	unsigned long *count_per_node = NULL;
-	int nid;
-	char kbuf[24];
-	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
-	ssize_t ret;
-
-	if (copy_from_user(kbuf, buf, read_len))
-		return -EFAULT;
-	kbuf[read_len] = '\0';
-
-	if (kstrtoul(kbuf, 10, &nr_to_scan))
-		return -EINVAL;
+	unsigned long *count_per_node;
+	unsigned long total, nr;
+	int ret, nid;
 
 	ret = down_read_killable(&shrinker_rwsem);
 	if (ret)
@@ -80,20 +67,7 @@  static ssize_t shrinker_debugfs_scan_write(struct file *file,
 			goto out;
 		}
 
-		for_each_node(nid) {
-			struct shrink_control sc = {
-				.gfp_mask = GFP_KERNEL,
-				.nid = nid,
-			};
-
-			nr = shrinker->count_objects(shrinker, &sc);
-			if (nr == SHRINK_EMPTY)
-				nr = 0;
-			count_per_node[nid] = nr;
-			total += nr;
-
-			cond_resched();
-		}
+		total = shrinker_count_objects(shrinker, memcg, count_per_node);
 	}
 
 	for_each_node(nid) {
@@ -102,13 +76,13 @@  static ssize_t shrinker_debugfs_scan_write(struct file *file,
 			.nid = nid,
 		};
 
-		if (shrinker->flags & SHRINKER_NUMA_AWARE) {
+		if (count_per_node) {
 			sc.nr_to_scan = nr_to_scan * count_per_node[nid] /
 				(total ? total : 1);
 			sc.nr_scanned = sc.nr_to_scan;
 		} else {
 			sc.nr_to_scan = nr_to_scan;
-			sc.nr_scanned = sc.nr_to_scan;
+			sc.nr_scanned = nr_to_scan;
 		}
 
 		nr = shrinker->scan_objects(shrinker, &sc);
@@ -119,15 +93,51 @@  static ssize_t shrinker_debugfs_scan_write(struct file *file,
 			break;
 
 		cond_resched();
-
 	}
-	ret = size;
 out:
 	up_read(&shrinker_rwsem);
 	kfree(count_per_node);
 	return ret;
 }
 
+static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
+{
+	struct shrinker *shrinker = (struct shrinker *)m->private;
+	int ret;
+
+	ret = down_read_killable(&shrinker_rwsem);
+	if (!ret) {
+		unsigned long total = shrinker_count_objects(shrinker, NULL, NULL);
+
+		up_read(&shrinker_rwsem);
+		seq_printf(m, "%lu\n", total);
+	}
+	return ret;
+}
+DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
+
+static ssize_t shrinker_debugfs_scan_write(struct file *file,
+					   const char __user *buf,
+					   size_t size, loff_t *pos)
+{
+	struct shrinker *shrinker = (struct shrinker *)file->private_data;
+	unsigned long nr_to_scan;
+	char kbuf[24];
+	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
+	ssize_t ret;
+
+	if (copy_from_user(kbuf, buf, read_len))
+		return -EFAULT;
+	kbuf[read_len] = '\0';
+
+	if (kstrtoul(kbuf, 10, &nr_to_scan))
+		return -EINVAL;
+
+	ret = shrinker_scan_objects(shrinker, NULL, nr_to_scan);
+
+	return ret ? ret : size;
+}
+
 static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
 {
 	file->private_data = inode->i_private;
@@ -140,6 +150,78 @@  static const struct file_operations shrinker_debugfs_scan_fops = {
 	.write	 = shrinker_debugfs_scan_write,
 };
 
+#ifdef CONFIG_MEMCG
+static int shrinker_debugfs_count_memcg_show(struct seq_file *m, void *v)
+{
+	struct shrinker *shrinker = (struct shrinker *)m->private;
+	struct mem_cgroup *memcg;
+	unsigned long total;
+	int ret;
+
+	ret = down_read_killable(&shrinker_rwsem);
+	if (ret)
+		return ret;
+	rcu_read_lock();
+
+	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+	do {
+		if (!mem_cgroup_online(memcg))
+			continue;
+
+		total = shrinker_count_objects(shrinker, memcg, NULL);
+		if (!total)
+			continue;
+
+		seq_printf(m, "%lu %lu\n", mem_cgroup_ino(memcg), total);
+	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+
+	rcu_read_unlock();
+	up_read(&shrinker_rwsem);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count_memcg);
+
+static ssize_t shrinker_debugfs_scan_memcg_write(struct file *file,
+						 const char __user *buf,
+						 size_t size, loff_t *pos)
+{
+	struct shrinker *shrinker = (struct shrinker *)file->private_data;
+	unsigned long nr_to_scan, ino;
+	struct mem_cgroup *memcg;
+	char kbuf[48];
+	int read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
+	ssize_t ret;
+
+	if (copy_from_user(kbuf, buf, read_len))
+		return -EFAULT;
+	kbuf[read_len] = '\0';
+
+	if (sscanf(kbuf, "%lu %lu", &ino, &nr_to_scan) < 2)
+		return -EINVAL;
+
+	memcg = mem_cgroup_get_from_ino(ino);
+	if (!memcg || IS_ERR(memcg))
+		return -ENOENT;
+
+	if (!mem_cgroup_online(memcg)) {
+		mem_cgroup_put(memcg);
+		return -ENOENT;
+	}
+
+	ret = shrinker_scan_objects(shrinker, memcg, nr_to_scan);
+	mem_cgroup_put(memcg);
+
+	return ret ? ret : size;
+}
+
+static const struct file_operations shrinker_debugfs_scan_memcg_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = shrinker_debugfs_scan_open,
+	.write	 = shrinker_debugfs_scan_memcg_write,
+};
+#endif
+
 int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
@@ -173,6 +255,16 @@  int shrinker_debugfs_add(struct shrinker *shrinker)
 	debugfs_create_file("scan", 0440, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
 
+#ifdef CONFIG_MEMCG
+	/* create memcg interfaces */
+	if (shrinker->flags & SHRINKER_MEMCG_AWARE) {
+		debugfs_create_file("count_memcg", 0220, entry, shrinker,
+				    &shrinker_debugfs_count_memcg_fops);
+		debugfs_create_file("scan_memcg", 0440, entry, shrinker,
+				    &shrinker_debugfs_scan_memcg_fops);
+	}
+#endif
+
 	return 0;
 }