Skip to contentSkip to conversation

memcg: inode-based dirty-set controller

RFC PATCH In review Konstantin Khebnikov 4 participants 14 messages Jan 15, 2015 – Jan 29, 2015

Subsystems (12)
lore
Share & tools
lore
#0 lore raw

cover memcg: inode-based dirty-set controller

This is ressurection of my old RFC patch for dirty-set accounting cgroup [1] Now it's merged into memory cgroup and got bandwidth controller as a bonus.

That shows alternative solution: less accurate but much less monstrous than accurate page-based dirty-set controller from Tejun Heo.

Memory overhead: +1 pointer into struct address_space. Perfomance overhead is almost zero, no new locks added.

Idea is stright forward: link each inode to some cgroup when first dirty page appers and account all dirty pages to it. Writeback is implemented as single per-bdi writeback work which writes only inodes which belong to memory cgroups where amount of dirty memory is beyond thresholds.

Third patch adds trick for handling shared inodes which have dirty pages from several cgroups: it marks whole inode as shared and alters writeback filter for it.

The rest is an example of bandwith and iops controller build on top of that. Design is completely original, I bet nobody ever used task-works for that =)

[1] [PATCH RFC] fsio: filesystem io accounting cgroup
http://marc.info/?l=linux-kernel&m=137331569501655&w=2

Patches also available here:
https://github.com/koct9i/linux.git branch memcg_dirty_control

---

Konstantin Khebnikov (6):
memcg: inode-based dirty and writeback pages accounting
memcg: dirty-set limiting and filtered writeback
memcg: track shared inodes with dirty pages
percpu_ratelimit: high-performance ratelimiting counter
delay-injection: resource management via procrastination
memcg: filesystem bandwidth controller

 block/blk-core.c                 |    2 
 fs/direct-io.c                   |    2 
 fs/fs-writeback.c                |   22 ++
 fs/inode.c                       |    1 
 include/linux/backing-dev.h      |    1 
 include/linux/fs.h               |   14 +
 include/linux/memcontrol.h       |   27 +++
 include/linux/percpu_ratelimit.h |   45 ++++
 include/linux/sched.h            |    7 +
 include/linux/writeback.h        |    1 
 include/trace/events/sched.h     |    7 +
 include/trace/events/writeback.h |    1 
 kernel/sched/core.c              |   66 +++++++
 kernel/sched/fair.c              |   12 +
 lib/Makefile                     |    1 
 lib/percpu_ratelimit.c           |  168 +++++++++++++++++
 mm/memcontrol.c                  |  381 ++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c              |   32 +++
 mm/readahead.c                   |    2 
 mm/truncate.c                    |    1 
 mm/vmscan.c                      |    4 
 21 files changed, 787 insertions(+), 10 deletions(-)
 create mode 100644 include/linux/percpu_ratelimit.h
 create mode 100644 lib/percpu_ratelimit.c

--
Signature

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#1 lore raw

PATCH 1/6 memcg: inode-based dirty and writeback pages accounting

From: Konstantin Khlebnikov <[email protected]>

This patch links memory cgroup into vfs layer and assigns owner memcg for each inode which has dirty or writeback pages within. The main goal of this is controlling dirty memory size.

Accounting dirty memory in per-inode manner is much easier (we've got locking for free) and more effective because we could use this information in in writeback and writeout only inodes which belongs to cgroup where amount of dirty memory is beyond of thresholds.

Interface: fs_dirty and fs_writeback in memory.stat attribute.

---
 fs/inode.c                 |    1 
 include/linux/fs.h         |   11 ++++
 include/linux/memcontrol.h |   13 +++++
 mm/memcontrol.c            |  118 ++++++++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c        |    7 ++-
 mm/truncate.c              |    1 
 6 files changed, 150 insertions(+), 1 deletion(-)
fs/inode.c +1 -0
index aa149e7..979a548 100644
@@ -559,6 +559,7 @@ static void evict(struct inode *inode)
 		bd_forget(inode);
 	if (S_ISCHR(inode->i_mode) && inode->i_cdev)
 		cd_forget(inode);
+	mem_cgroup_forget_mapping(&inode->i_data);
 
 	remove_inode_hash(inode);
 
include/linux/fs.h +11 -0
index 42efe13..ee2e3c0 100644
@@ -413,6 +413,9 @@ struct address_space {
 	spinlock_t		private_lock;	/* for use by the address_space */
 	struct list_head	private_list;	/* ditto */
 	void			*private_data;	/* ditto */
+#ifdef CONFIG_MEMCG
+	struct mem_cgroup __rcu	*i_memcg;	/* protected by ->tree_lock */
+#endif
 } __attribute__((aligned(sizeof(long))));
 	/*
 	 * On most architectures that alignment is already the case; but
@@ -489,6 +492,14 @@ static inline void i_mmap_unlock_read(struct address_space *mapping)
 }
 
 /*
+ * Returns bitmap with all page-cache radix-tree tags
+ */
+static inline unsigned mapping_tags(struct address_space *mapping)
+{
+	return (__force unsigned)mapping->page_tree.gfp_mask >> __GFP_BITS_SHIFT;
+}
+
+/*
  * Might pages of this file be mapped into userspace?
  */
 static inline int mapping_mapped(struct address_space *mapping)
include/linux/memcontrol.h +13 -0
index 7c95af8..b281333 100644
@@ -173,6 +173,12 @@ static inline void mem_cgroup_count_vm_event(struct mm_struct *mm,
 void mem_cgroup_split_huge_fixup(struct page *head);
 #endif
 
+void mem_cgroup_inc_page_dirty(struct address_space *mapping);
+void mem_cgroup_dec_page_dirty(struct address_space *mapping);
+void mem_cgroup_inc_page_writeback(struct address_space *mapping);
+void mem_cgroup_dec_page_writeback(struct address_space *mapping);
+void mem_cgroup_forget_mapping(struct address_space *mapping);
+
 #else /* CONFIG_MEMCG */
 struct mem_cgroup;
 
@@ -340,6 +346,13 @@ static inline
 void mem_cgroup_count_vm_event(struct mm_struct *mm, enum vm_event_item idx)
 {
 }
+
+static inline void mem_cgroup_inc_page_dirty(struct address_space *mapping) {}
+static inline void mem_cgroup_dec_page_dirty(struct address_space *mapping) {}
+static inline void mem_cgroup_inc_page_writeback(struct address_space *mapping) {}
+static inline void mem_cgroup_dec_page_writeback(struct address_space *mapping) {}
+static inline void mem_cgroup_forget_mapping(struct address_space *mapping) {}
+
 #endif /* CONFIG_MEMCG */
 
 enum {
mm/memcontrol.c +118 -0

166 lines not on this page · raw message

mm/page-writeback.c +6 -1
index 6f43352..afaf263 100644
@@ -2098,6 +2098,7 @@ void account_page_dirtied(struct page *page, struct address_space *mapping)
 		__inc_zone_page_state(page, NR_DIRTIED);
 		__inc_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
 		__inc_bdi_stat(mapping->backing_dev_info, BDI_DIRTIED);
+		mem_cgroup_inc_page_dirty(mapping);
 		task_io_account_write(PAGE_CACHE_SIZE);
 		current->nr_dirtied++;
 		this_cpu_inc(bdp_ratelimits);
@@ -2297,6 +2298,7 @@ int clear_page_dirty_for_io(struct page *page)
 			dec_zone_page_state(page, NR_FILE_DIRTY);
 			dec_bdi_stat(mapping->backing_dev_info,
 					BDI_RECLAIMABLE);
+			mem_cgroup_dec_page_dirty(mapping);
 			return 1;
 		}
 		return 0;
@@ -2327,6 +2329,7 @@ int test_clear_page_writeback(struct page *page)
 			if (bdi_cap_account_writeback(bdi)) {
 				__dec_bdi_stat(bdi, BDI_WRITEBACK);
 				__bdi_writeout_inc(bdi);
+				mem_cgroup_dec_page_writeback(mapping);
 			}
 		}
 		spin_unlock_irqrestore(&mapping->tree_lock, flags);
@@ -2361,8 +2364,10 @@ int __test_set_page_writeback(struct page *page, bool keep_write)
 			radix_tree_tag_set(&mapping->page_tree,
 						page_index(page),
 						PAGECACHE_TAG_WRITEBACK);
-			if (bdi_cap_account_writeback(bdi))
+			if (bdi_cap_account_writeback(bdi)) {
 				__inc_bdi_stat(bdi, BDI_WRITEBACK);
+				mem_cgroup_inc_page_writeback(mapping);
+			}
 		}
 		if (!PageDirty(page))
 			radix_tree_tag_clear(&mapping->page_tree,
mm/truncate.c +1 -0
index f1e4d60..37915fe 100644
@@ -114,6 +114,7 @@ void cancel_dirty_page(struct page *page, unsigned int account_size)
 			dec_zone_page_state(page, NR_FILE_DIRTY);
 			dec_bdi_stat(mapping->backing_dev_info,
 					BDI_RECLAIMABLE);
+			mem_cgroup_dec_page_dirty(mapping);
 			if (account_size)
 				task_io_account_cancelled_write(account_size);
 		}

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#2 lore raw

PATCH 2/6 memcg: dirty-set limiting and filtered writeback

In reply to Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

mem_cgroup_dirty_limits() checks thresholds and schedules per-bdi writeback work (where ->for_memcg is set) which writes only inodes where dirty limit is exceeded for owner memcg or for whole bdi.

Interface: memory.dirty_ratio percent of memory limit used as threshold (0 = unlimited, default 50). Background threshold is a half of that. And fs_dirty_threshold line in memory.stat shows current threshold.

---
 fs/fs-writeback.c                |   18 ++++-
 include/linux/backing-dev.h      |    1 
 include/linux/memcontrol.h       |    6 ++
 include/linux/writeback.h        |    1 
 include/trace/events/writeback.h |    1 
 mm/memcontrol.c                  |  145 ++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c              |   25 ++++++-
 7 files changed, 190 insertions(+), 7 deletions(-)
fs/fs-writeback.c +15 -3
index 2d609a5..9034768 100644
@@ -20,6 +20,7 @@
 #include <linux/sched.h>
 #include <linux/fs.h>
 #include <linux/mm.h>
+#include <linux/memcontrol.h>
 #include <linux/pagemap.h>
 #include <linux/kthread.h>
 #include <linux/writeback.h>
@@ -47,6 +48,7 @@ struct wb_writeback_work {
 	unsigned int range_cyclic:1;
 	unsigned int for_background:1;
 	unsigned int for_sync:1;	/* sync(2) WB_SYNC_ALL writeback */
+	unsigned int for_memcg:1;
 	enum wb_reason reason;		/* why was writeback initiated? */
 
 	struct list_head list;		/* pending work list */
@@ -137,6 +139,7 @@ __bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages,
 	work->nr_pages	= nr_pages;
 	work->range_cyclic = range_cyclic;
 	work->reason	= reason;
+	work->for_memcg = reason == WB_REASON_FOR_MEMCG;
 
 	bdi_queue_work(bdi, work);
 }
@@ -258,15 +261,16 @@ static int move_expired_inodes(struct list_head *delaying_queue,
 	LIST_HEAD(tmp);
 	struct list_head *pos, *node;
 	struct super_block *sb = NULL;
-	struct inode *inode;
+	struct inode *inode, *next;
 	int do_sb_sort = 0;
 	int moved = 0;
 
-	while (!list_empty(delaying_queue)) {
-		inode = wb_inode(delaying_queue->prev);
+	list_for_each_entry_safe(inode, next, delaying_queue, i_wb_list) {
 		if (work->older_than_this &&
 		    inode_dirtied_after(inode, *work->older_than_this))
 			break;
+		if (work->for_memcg && !mem_cgroup_dirty_exceeded(inode))
+			continue;
 		list_move(&inode->i_wb_list, &tmp);
 		moved++;
 		if (sb_is_blkdev_sb(inode->i_sb))
@@ -650,6 +654,11 @@ static long writeback_sb_inodes(struct super_block *sb,
 			break;
 		}
 
+		if (work->for_memcg && !mem_cgroup_dirty_exceeded(inode)) {
+			redirty_tail(inode, wb);
+			continue;
+		}
+
 		/*
 		 * Don't bother with new inodes or inodes being freed, first
 		 * kind does not need periodic writeout yet, and for the latter
@@ -1014,6 +1023,9 @@ static long wb_do_writeback(struct bdi_writeback *wb)
 
 		wrote += wb_writeback(wb, work);
 
+		if (work->for_memcg)
+			clear_bit(BDI_memcg_writeback_running, &bdi->state);
+
 		/*
 		 * Notify the caller of completion if this is a synchronous
 		 * work item, otherwise just free it.
include/linux/backing-dev.h +1 -0
index 5da6012..91b55d8 100644
@@ -32,6 +32,7 @@ enum bdi_state {
 	BDI_sync_congested,	/* The sync queue is getting full */
 	BDI_registered,		/* bdi_register() was done */
 	BDI_writeback_running,	/* Writeback is in progress */
+	BDI_memcg_writeback_running,
 };
 
 typedef int (congested_fn)(void *, int);
include/linux/memcontrol.h +6 -0
index b281333..ae05563 100644
@@ -178,6 +178,9 @@ void mem_cgroup_dec_page_dirty(struct address_space *mapping);
 void mem_cgroup_inc_page_writeback(struct address_space *mapping);
 void mem_cgroup_dec_page_writeback(struct address_space *mapping);
 void mem_cgroup_forget_mapping(struct address_space *mapping);
+bool mem_cgroup_dirty_limits(struct address_space *mapping, unsigned long *dirty,
+			     unsigned long *thresh, unsigned long *bg_thresh);
+bool mem_cgroup_dirty_exceeded(struct inode *inode);
 
 #else /* CONFIG_MEMCG */
 struct mem_cgroup;
@@ -352,6 +355,9 @@ static inline void mem_cgroup_dec_page_dirty(struct address_space *mapping) {}
 static inline void mem_cgroup_inc_page_writeback(struct address_space *mapping) {}
 static inline void mem_cgroup_dec_page_writeback(struct address_space *mapping) {}
 static inline void mem_cgroup_forget_mapping(struct address_space *mapping) {}
+static inline bool mem_cgroup_dirty_limits(struct address_space *mapping, unsigned long *dirty,
+			     unsigned long *thresh, unsigned long *bg_thresh) { return false; }
+static inline bool mem_cgroup_dirty_exceeded(struct inode *inode) { return false; }
 
 #endif /* CONFIG_MEMCG */
 
include/linux/writeback.h +1 -0
index 0004833..1239fa6 100644
@@ -47,6 +47,7 @@ enum wb_reason {
 	WB_REASON_LAPTOP_TIMER,
 	WB_REASON_FREE_MORE_MEM,
 	WB_REASON_FS_FREE_SPACE,
+	WB_REASON_FOR_MEMCG,
 	/*
 	 * There is no bdi forker thread any more and works are done
 	 * by emergency worker, however, this is TPs userland visible
include/trace/events/writeback.h +1 -0
index cee02d6..106a8d7 100644
@@ -29,6 +29,7 @@
 		{WB_REASON_LAPTOP_TIMER,	"laptop_timer"},	\
 		{WB_REASON_FREE_MORE_MEM,	"free_more_memory"},	\
 		{WB_REASON_FS_FREE_SPACE,	"fs_free_space"},	\
+		{WB_REASON_FOR_MEMCG,		"for_memcg"},		\
 		{WB_REASON_FORKER_THREAD,	"forker_thread"}
 
 struct wb_writeback_work;
mm/memcontrol.c +145 -0

199 lines not on this page · raw message

mm/page-writeback.c +21 -4
index afaf263..325510f 100644
@@ -1328,6 +1328,17 @@ static inline void bdi_dirty_limits(struct backing_dev_info *bdi,
 	}
 }
 
+static unsigned long mem_cgroup_position_ratio(unsigned long dirty,
+		unsigned long thresh, unsigned long bg_thresh)
+{
+	unsigned long setpoint = dirty_freerun_ceiling(thresh, bg_thresh);
+
+	if (dirty > thresh)
+		return 0;
+
+	return pos_ratio_polynom(setpoint, dirty, thresh);
+}
+
 /*
  * balance_dirty_pages() must be called by processes which are generating dirty
  * data.  It looks at the number of dirty pages in the machine and will force
@@ -1362,6 +1373,7 @@ static void balance_dirty_pages(struct address_space *mapping,
 		unsigned long uninitialized_var(bdi_dirty);
 		unsigned long dirty;
 		unsigned long bg_thresh;
+		bool memcg;
 
 		/*
 		 * Unstable writes are a feature of certain networked
@@ -1387,6 +1399,8 @@ static void balance_dirty_pages(struct address_space *mapping,
 			bg_thresh = background_thresh;
 		}
 
+		memcg = mem_cgroup_dirty_limits(mapping, &dirty, &thresh, &bg_thresh);
+
 		/*
 		 * Throttle it only when the background writeback cannot
 		 * catch-up. This avoids (excessively) small writeouts
@@ -1404,7 +1418,7 @@ static void balance_dirty_pages(struct address_space *mapping,
 			break;
 		}
 
-		if (unlikely(!writeback_in_progress(bdi)))
+		if (unlikely(!writeback_in_progress(bdi) && !memcg))
 			bdi_start_background_writeback(bdi);
 
 		if (!strictlimit)
@@ -1421,9 +1435,12 @@ static void balance_dirty_pages(struct address_space *mapping,
 				     start_time);
 
 		dirty_ratelimit = bdi->dirty_ratelimit;
-		pos_ratio = bdi_position_ratio(bdi, dirty_thresh,
-					       background_thresh, nr_dirty,
-					       bdi_thresh, bdi_dirty);
+		if (memcg)
+			pos_ratio = mem_cgroup_position_ratio(dirty, thresh, bg_thresh);
+		else
+			pos_ratio = bdi_position_ratio(bdi, dirty_thresh,
+					background_thresh, nr_dirty,
+					bdi_thresh, bdi_dirty);
 		task_ratelimit = ((u64)dirty_ratelimit * pos_ratio) >>
 							RATELIMIT_CALC_SHIFT;
 		max_pause = bdi_max_pause(bdi, bdi_dirty);

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#3 lore raw

PATCH 3/6 memcg: track shared inodes with dirty pages

In reply to Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

Inode is owned only by one memory cgroup, but if it's shared it might contain pages from multiple cgroups. This patch detects this situation in memory reclaiemer and marks dirty inode with flag I_DIRTY_SHARED which is cleared only when data is completely written. Memcg writeback always writes such inodes.

---
 fs/fs-writeback.c          |    4 ++--
 include/linux/fs.h         |    3 +++
 include/linux/memcontrol.h |    4 ++++
 mm/memcontrol.c            |   20 ++++++++++++++++++++
 mm/vmscan.c                |    4 ++++
 5 files changed, 33 insertions(+), 2 deletions(-)
fs/fs-writeback.c +2 -2
index 9034768..fda6a64 100644
@@ -484,7 +484,7 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
 	 */
 	spin_lock(&inode->i_lock);
 
-	dirty = inode->i_state & I_DIRTY;
+	dirty = inode->i_state & (I_DIRTY | I_DIRTY_SHARED);
 	inode->i_state &= ~I_DIRTY;
 
 	/*
@@ -501,7 +501,7 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
 	smp_mb();
 
 	if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
-		inode->i_state |= I_DIRTY_PAGES;
+		inode->i_state |= I_DIRTY_PAGES | (dirty & I_DIRTY_SHARED);
 
 	spin_unlock(&inode->i_lock);
 
include/linux/fs.h +3 -0
index ee2e3c0..303f0ad 100644
@@ -1741,6 +1741,8 @@ struct super_operations {
  *
  * I_DIO_WAKEUP		Never set.  Only used as a key for wait_on_bit().
  *
+ * I_DIRTY_SHARED	Dirty pages belong to multiple memory cgroups.
+ *
  * Q: What is the difference between I_WILL_FREE and I_FREEING?
  */
 #define I_DIRTY_SYNC		(1 << 0)
@@ -1757,6 +1759,7 @@ struct super_operations {
 #define __I_DIO_WAKEUP		9
 #define I_DIO_WAKEUP		(1 << I_DIO_WAKEUP)
 #define I_LINKABLE		(1 << 10)
+#define I_DIRTY_SHARED		(1 << 11)
 
 #define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
 
include/linux/memcontrol.h +4 -0
index ae05563..3f89e9b 100644
@@ -181,6 +181,8 @@ void mem_cgroup_forget_mapping(struct address_space *mapping);
 bool mem_cgroup_dirty_limits(struct address_space *mapping, unsigned long *dirty,
 			     unsigned long *thresh, unsigned long *bg_thresh);
 bool mem_cgroup_dirty_exceeded(struct inode *inode);
+void mem_cgroup_poke_writeback(struct address_space *mapping,
+			       struct mem_cgroup *memcg);
 
 #else /* CONFIG_MEMCG */
 struct mem_cgroup;
@@ -358,6 +360,8 @@ static inline void mem_cgroup_forget_mapping(struct address_space *mapping) {}
 static inline bool mem_cgroup_dirty_limits(struct address_space *mapping, unsigned long *dirty,
 			     unsigned long *thresh, unsigned long *bg_thresh) { return false; }
 static inline bool mem_cgroup_dirty_exceeded(struct inode *inode) { return false; }
+static inline void mem_cgroup_poke_writeback(struct address_space *mapping,
+					     struct mem_cgroup *memcg) { }
 
 #endif /* CONFIG_MEMCG */
 
mm/memcontrol.c +20 -0
index 17d966a3b..d9d345c 100644
@@ -6064,6 +6064,9 @@ bool mem_cgroup_dirty_exceeded(struct inode *inode)
 	if (mapping->backing_dev_info->dirty_exceeded)
 		return true;
 
+	if (inode->i_state & I_DIRTY_SHARED)
+		return true;
+
 	rcu_read_lock();
 	memcg = rcu_dereference(mapping->i_memcg);
 	for (; memcg; memcg = parent_mem_cgroup(memcg)) {
@@ -6084,6 +6087,23 @@ bool mem_cgroup_dirty_exceeded(struct inode *inode)
 	return memcg != NULL;
 }
 
+void mem_cgroup_poke_writeback(struct address_space *mapping,
+			       struct mem_cgroup *memcg)
+{
+	struct inode *inode = mapping->host;
+
+	if (rcu_access_pointer(mapping->i_memcg) == memcg ||
+	    !memcg->dirty_exceeded)
+		return;
+
+	if (inode->i_state & (I_DIRTY_PAGES|I_DIRTY_SHARED) == I_DIRTY_PAGES) {
+		spin_lock(&inode->i_lock);
+		if (inode->i_state & I_DIRTY_PAGES)
+			inode->i_state |= I_DIRTY_SHARED;
+		spin_unlock(&inode->i_lock);
+	}
+}
+
 /*
  * subsys_initcall() for memory controller.
  *
mm/vmscan.c +4 -0
index ab2505c..75165fc 100644
@@ -1013,6 +1013,10 @@ static unsigned long shrink_page_list(struct list_head *page_list,
 				inc_zone_page_state(page, NR_VMSCAN_IMMEDIATE);
 				SetPageReclaim(page);
 
+				if (!global_reclaim(sc))
+					mem_cgroup_poke_writeback(mapping,
+							sc->target_mem_cgroup);
+
 				goto keep_locked;
 			}
 

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#4 lore raw

Re: [PATCH 3/6] memcg: track shared inodes with dirty pages

Quoted 9 lines · Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

Inode is owned only by one memory cgroup, but if it's shared it might contain pages from multiple cgroups. This patch detects this situation in memory reclaiemer and marks dirty inode with flag I_DIRTY_SHARED which is cleared only when data is completely written. Memcg writeback always writes such inodes.

This conflicts with the writeback cgroup support patchset which will solve the writeback and memcg problem a lot more comprehensively.

http://lkml.kernel.org/g/[email protected]

Thanks.

Signature · 7 lines
tejun

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#5 lore raw

Re: [PATCH 3/6] memcg: track shared inodes with dirty pages

Quoted 9 lines · Tejun Heo
Quoted 8 lines · Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

Inode is owned only by one memory cgroup, but if it's shared it might contain pages from multiple cgroups. This patch detects this situation in memory reclaiemer and marks dirty inode with flag I_DIRTY_SHARED which is cleared only when data is completely written. Memcg writeback always writes such inodes.

Quoted 8 lines
Quoted 1 line

This conflicts with the writeback cgroup support patchset which will solve the writeback and memcg problem a lot more comprehensively.

http://lkml.kernel.org/g/[email protected]

Thanks.

I know. Absolutely accurate per-page solution looks too complicated for me. Is there any real demand for accurate handling dirty set in shared inodes? Doing whole accounting in per-inode basis makes life so much easier.

Quoted 3 lines

--
tejun

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#6 lore raw

Re: [PATCH 3/6] memcg: track shared inodes with dirty pages

Hello,

Quoted 3 lines · Konstantin Khlebnikov

I know. Absolutely accurate per-page solution looks too complicated for me. Is there any real demand for accurate handling dirty set in shared inodes? Doing whole accounting in per-inode basis makes life so much easier.

Ah, yeah, patch #3 arrived in isolation, so I thought it was part of something completely different. I definitely thought about doing it per-inode too (and also requiring memcg to attribute pages according to its inode rather than individual pages). I'll look into the patchset and try to identify the pros and cons of our approaches.

Thanks.

Signature · 7 lines
tejun

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#7 lore raw

PATCH 4/6 percpu_ratelimit: high-performance ratelimiting counter

In reply to Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

Parameters:
period - interval between refills (100ms should be fine)
quota - events refill per period
deadline - interval to utilize unused past quota (1s by default) latency - maximum injected delay (10s by default)

Quota sums into 'budget' and spreads across cpus.

---
 include/linux/percpu_ratelimit.h |   45 ++++++++++
 lib/Makefile                     |    1 
 lib/percpu_ratelimit.c           |  168 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 214 insertions(+)
 create mode 100644 include/linux/percpu_ratelimit.h
 create mode 100644 lib/percpu_ratelimit.c
include/linux/percpu_ratelimit.h new file +45 -0
new file mode 100644
index 0000000..42c45d4
@@ -0,0 +1,45 @@
+#ifndef _LINUX_PERCPU_RATELIMIT_H
+#define _LINUX_PERCPU_RATELIMIT_H
+
+#include <linux/hrtimer.h>
+
+struct percpu_ratelimit {
+	struct hrtimer  timer;
+	ktime_t		target;		/* time of next refill */
+	ktime_t		deadline;	/* interval to utilize past budget */
+	ktime_t		latency;	/* maximum injected delay */
+	ktime_t		period;		/* interval between refills */
+	u64		quota;		/* events refill per period */
+	u64		budget;		/* amount of available events */
+	u64		total;		/* consumed and pre-charged events */
+	raw_spinlock_t	lock;		/* protect the state */
+	u32		cpu_batch;	/* events in per-cpu precharge */
+	u32 __percpu	*cpu_budget;	/* per-cpu precharge */
+};
+
+static inline bool percpu_ratelimit_blocked(struct percpu_ratelimit *rl)
+{
+       return hrtimer_active(&rl->timer);
+}
+
+static inline ktime_t percpu_ratelimit_target(struct percpu_ratelimit *rl)
+{
+	return rl->target;
+}
+
+static inline int percpu_ratelimit_wait(struct percpu_ratelimit *rl)
+{
+	ktime_t target = rl->target;
+
+	return schedule_hrtimeout_range(&target, ktime_to_ns(rl->period),
+					HRTIMER_MODE_ABS);
+}
+
+int percpu_ratelimit_init(struct percpu_ratelimit *rl, gfp_t gfp);
+void percpu_ratelimit_destroy(struct percpu_ratelimit *rl);
+void percpu_ratelimit_setup(struct percpu_ratelimit *rl, u64 quota, u64 period);
+u64 percpu_ratelimit_quota(struct percpu_ratelimit *rl, u64 period);
+bool percpu_ratelimit_charge(struct percpu_ratelimit *rl, u64 events);
+u64 percpu_ratelimit_sum(struct percpu_ratelimit *rl);
+
+#endif /* _LINUX_PERCPU_RATELIMIT_H */
lib/Makefile +1 -0
index 3c3b30b..b20ab47 100644
@@ -21,6 +21,7 @@ lib-$(CONFIG_SMP) += cpumask.o
 
 lib-y	+= kobject.o klist.o
 obj-y	+= lockref.o
+obj-y   += percpu_ratelimit.o
 
 obj-y += bcd.o div64.o sort.o parser.o halfmd4.o debug_locks.o random32.o \
 	 bust_spinlocks.o hexdump.o kasprintf.o bitmap.o scatterlist.o \
lib/percpu_ratelimit.c new file +168 -0

168 lines not on this page · raw message

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#8 lore raw

PATCH 5/6 delay-injection: resource management via procrastination

In reply to Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

inject_delay() allows to pause current task before returning into userspace in place where kernel doesn't hold any locks thus wait wouldn't introduce any priority-inversion problems.

This code abuses existing task-work and 'TASK_PARKED' state. Parked tasks are killable and don't contribute into cpu load.

Together with percpu_ratelimit this could be used in this manner:

if (percpu_ratelimit_charge(&ratelimit, events))

        inject_delay(percpu_ratelimit_target(&ratelimit));
---
 include/linux/sched.h        |    7 ++++
 include/trace/events/sched.h |    7 ++++
 kernel/sched/core.c          |   66 ++++++++++++++++++++++++++++++++++++++++++
 kernel/sched/fair.c          |   12 ++++++++
 4 files changed, 92 insertions(+)
include/linux/sched.h +7 -0
index 8db31ef..2363918 100644
@@ -1132,6 +1132,7 @@ struct sched_statistics {
 	u64			iowait_sum;
 
 	u64			sleep_start;
+	u64			delay_start;
 	u64			sleep_max;
 	s64			sum_sleep_runtime;
 
@@ -1662,6 +1663,10 @@ struct task_struct {
 	unsigned long timer_slack_ns;
 	unsigned long default_timer_slack_ns;
 
+	/* Pause task till this time before returning into userspace */
+	ktime_t delay_injection_target;
+	struct callback_head delay_injection_work;
+
 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
 	/* Index of current stored address in ret_stack */
 	int curr_ret_stack;
@@ -2277,6 +2282,8 @@ extern void set_curr_task(int cpu, struct task_struct *p);
 
 void yield(void);
 
+extern void inject_delay(ktime_t target);
+
 /*
  * The default (Linux) execution domain.
  */
include/trace/events/sched.h +7 -0
index 30fedaf..d35154e 100644
@@ -365,6 +365,13 @@ DEFINE_EVENT(sched_stat_template, sched_stat_blocked,
 	     TP_ARGS(tsk, delay));
 
 /*
+ * Tracepoint for accounting delay-injection
+ */
+DEFINE_EVENT(sched_stat_template, sched_stat_delayed,
+	     TP_PROTO(struct task_struct *tsk, u64 delay),
+	     TP_ARGS(tsk, delay));
+
+/*
  * Tracepoint for accounting runtime (time the task is executing
  * on a CPU).
  */
kernel/sched/core.c +66 -0

75 lines not on this page · raw message

kernel/sched/fair.c +12 -0
index 40667cb..2e3269b 100644
@@ -2944,6 +2944,15 @@ static void enqueue_sleeper(struct cfs_rq *cfs_rq, struct sched_entity *se)
 			account_scheduler_latency(tsk, delta >> 10, 0);
 		}
 	}
+	if (se->statistics.delay_start) {
+		u64 delta = rq_clock(rq_of(cfs_rq)) - se->statistics.delay_start;
+
+		if ((s64)delta < 0)
+			delta = 0;
+
+		se->statistics.delay_start = 0;
+		trace_sched_stat_delayed(tsk, delta);
+	}
 #endif
 }
 
@@ -3095,6 +3104,9 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
 				se->statistics.sleep_start = rq_clock(rq_of(cfs_rq));
 			if (tsk->state & TASK_UNINTERRUPTIBLE)
 				se->statistics.block_start = rq_clock(rq_of(cfs_rq));
+			if ((tsk->state & TASK_PARKED) &&
+			    tsk->delay_injection_target.tv64)
+				se->statistics.delay_start = rq_clock(rq_of(cfs_rq));
 		}
 #endif
 	}

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#9 lore raw

PATCH 6/6 memcg: filesystem bandwidth controller

In reply to Konstantin Khebnikov

From: Konstantin Khlebnikov <[email protected]>

This is example of filesystem bandwidth controller build on the top of dirty memory accounting, percpu_ratelimit and delay-injection.

Cgroup charges read/write requests into rate-limiters and injects delays which controls overall speed.

Interface:
memory.fs_bps_limit bytes per second, 0 == unlimited
memory.fs_iops_limit iops limit, 0 == unlimited
Statistics: fs_io_bytes and fs_io_operations in memory.stat

For small bandwidth limits memory limit also must be set into corresponded value otherwise injected delay after writing dirty-set might be enormous.

---
 block/blk-core.c           |    2 +
 fs/direct-io.c             |    2 +
 include/linux/memcontrol.h |    4 ++
 mm/memcontrol.c            |  102 +++++++++++++++++++++++++++++++++++++++++++-
 mm/readahead.c             |    2 +
 5 files changed, 110 insertions(+), 2 deletions(-)
block/blk-core.c +2 -0
index 3ad4055..799f5f5 100644
@@ -1966,6 +1966,7 @@ void submit_bio(int rw, struct bio *bio)
 			count_vm_events(PGPGOUT, count);
 		} else {
 			task_io_account_read(bio->bi_iter.bi_size);
+			mem_cgroup_account_bandwidth(bio->bi_iter.bi_size);
 			count_vm_events(PGPGIN, count);
 		}
 
@@ -2208,6 +2209,7 @@ void blk_account_io_start(struct request *rq, bool new_io)
 		}
 		part_round_stats(cpu, part);
 		part_inc_in_flight(part, rw);
+		mem_cgroup_account_ioop();
 		rq->part = part;
 	}
 
fs/direct-io.c +2 -0
index e181b6b..9c60a82 100644
@@ -24,6 +24,7 @@
 #include <linux/types.h>
 #include <linux/fs.h>
 #include <linux/mm.h>
+#include <linux/memcontrol.h>
 #include <linux/slab.h>
 #include <linux/highmem.h>
 #include <linux/pagemap.h>
@@ -775,6 +776,7 @@ submit_page_section(struct dio *dio, struct dio_submit *sdio, struct page *page,
 		 * Read accounting is performed in submit_bio()
 		 */
 		task_io_account_write(len);
+		mem_cgroup_account_bandwidth(len);
 	}
 
 	/*
include/linux/memcontrol.h +4 -0
index 3f89e9b..633310e 100644
@@ -183,6 +183,8 @@ bool mem_cgroup_dirty_limits(struct address_space *mapping, unsigned long *dirty
 bool mem_cgroup_dirty_exceeded(struct inode *inode);
 void mem_cgroup_poke_writeback(struct address_space *mapping,
 			       struct mem_cgroup *memcg);
+void mem_cgroup_account_bandwidth(unsigned long bytes);
+void mem_cgroup_account_ioop(void);
 
 #else /* CONFIG_MEMCG */
 struct mem_cgroup;
@@ -362,6 +364,8 @@ static inline bool mem_cgroup_dirty_limits(struct address_space *mapping, unsign
 static inline bool mem_cgroup_dirty_exceeded(struct inode *inode) { return false; }
 static inline void mem_cgroup_poke_writeback(struct address_space *mapping,
 					     struct mem_cgroup *memcg) { }
+static inline void mem_cgroup_account_bandwidth(unsigned long bytes) {}
+static inline void mem_cgroup_account_ioop(void) {}
 
 #endif /* CONFIG_MEMCG */
 
mm/memcontrol.c +100 -2

163 lines not on this page · raw message

mm/readahead.c +2 -0
index 17b9172..7c7ec23 100644
@@ -16,6 +16,7 @@
 #include <linux/pagevec.h>
 #include <linux/pagemap.h>
 #include <linux/syscalls.h>
+#include <linux/memcontrol.h>
 #include <linux/file.h>
 
 #include "internal.h"
@@ -102,6 +103,7 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages,
 			break;
 		}
 		task_io_account_read(PAGE_CACHE_SIZE);
+		mem_cgroup_account_bandwidth(PAGE_CACHE_SIZE);
 	}
 	return ret;
 }

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#10 lore raw
In reply to Konstantin Khebnikov

Hello,

Quoted 14 lines · Konstantin Khebnikov

This is ressurection of my old RFC patch for dirty-set accounting cgroup [1] Now it's merged into memory cgroup and got bandwidth controller as a bonus.

That shows alternative solution: less accurate but much less monstrous than accurate page-based dirty-set controller from Tejun Heo.

Memory overhead: +1 pointer into struct address_space. Perfomance overhead is almost zero, no new locks added.

Idea is stright forward: link each inode to some cgroup when first dirty page appers and account all dirty pages to it. Writeback is implemented as single per-bdi writeback work which writes only inodes which belong to memory cgroups where amount of dirty memory is beyond thresholds.

Quoted 6 lines

Third patch adds trick for handling shared inodes which have dirty pages from several cgroups: it marks whole inode as shared and alters writeback filter for it.

The rest is an example of bandwith and iops controller build on top of that. Design is completely original, I bet nobody ever used task-works for that =)

So I like the simplicity of your code but there are a few downsides too
(please correct me if I've got something wrong - btw the documentation of high-level design would be welcome so that one doesn't have to understand that from the patches):
1) The bandwidth controller simply accounts number of bytes submitted for IO in submit_bio(). This doesn't reflect HW capabilities in any way. There a huge difference between a process submitting single block random IO and a process doing the same amount of sequential IO. This could be somewhat dealt with by not accounting number of bytes but rather time it took to complete a bio (but that somewhat complicates the code and block layer already does similar counting so it would be good you used that).

2) The controller accounts bio to current task - that makes the limiting useless for background writeback. You need to somehow propagate i_memcg into submit_bio() so that IO is properly accounted.

3) The controller doesn't seem to guarantee any quality of service at the IO level like blkcg does. The controller limits amount of IO userspace is able to submit to kernel but only after we decide to submit the IO to disk. So at that time cgroup may have generated lots of IO - e.g. by dirtying lots of pages - and there's nothing to protect other cgroups from starvation because of writeback of these pages.

Especially the last point seems to be essential to your approach (although you could somewhat mitigate the issue by accounting the IO already when it is entering the kernel) and I'm not sure whether that's really acceptable for potential users of this feature.

Honza

Quoted 44 lines

[1] [PATCH RFC] fsio: filesystem io accounting cgroup
http://marc.info/?l=linux-kernel&m=137331569501655&w=2

Patches also available here:
https://github.com/koct9i/linux.git branch memcg_dirty_control

---

Konstantin Khebnikov (6):
memcg: inode-based dirty and writeback pages accounting
memcg: dirty-set limiting and filtered writeback
memcg: track shared inodes with dirty pages
percpu_ratelimit: high-performance ratelimiting counter
delay-injection: resource management via procrastination
memcg: filesystem bandwidth controller

 block/blk-core.c                 |    2 
 fs/direct-io.c                   |    2 
 fs/fs-writeback.c                |   22 ++
 fs/inode.c                       |    1 
 include/linux/backing-dev.h      |    1 
 include/linux/fs.h               |   14 +
 include/linux/memcontrol.h       |   27 +++
 include/linux/percpu_ratelimit.h |   45 ++++
 include/linux/sched.h            |    7 +
 include/linux/writeback.h        |    1 
 include/trace/events/sched.h     |    7 +
 include/trace/events/writeback.h |    1 
 kernel/sched/core.c              |   66 +++++++
 kernel/sched/fair.c              |   12 +
 lib/Makefile                     |    1 
 lib/percpu_ratelimit.c           |  168 +++++++++++++++++
 mm/memcontrol.c                  |  381 ++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c              |   32 +++
 mm/readahead.c                   |    2 
 mm/truncate.c                    |    1 
 mm/vmscan.c                      |    4 
 21 files changed, 787 insertions(+), 10 deletions(-)
 create mode 100644 include/linux/percpu_ratelimit.h
 create mode 100644 lib/percpu_ratelimit.c

--
Signature

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
#11 lore raw
Quoted 21 lines · Jan Kara

Hello,

Quoted 18 lines · Konstantin Khebnikov

This is ressurection of my old RFC patch for dirty-set accounting cgroup [1] Now it's merged into memory cgroup and got bandwidth controller as a bonus.

That shows alternative solution: less accurate but much less monstrous than accurate page-based dirty-set controller from Tejun Heo.

Memory overhead: +1 pointer into struct address_space. Perfomance overhead is almost zero, no new locks added.

Idea is stright forward: link each inode to some cgroup when first dirty page appers and account all dirty pages to it. Writeback is implemented as single per-bdi writeback work which writes only inodes which belong to memory cgroups where amount of dirty memory is beyond thresholds.

Third patch adds trick for handling shared inodes which have dirty pages from several cgroups: it marks whole inode as shared and alters writeback filter for it.

Quoted 6 lines
Quoted 2 lines

The rest is an example of bandwith and iops controller build on top of that. Design is completely original, I bet nobody ever used task-works for that =)

So I like the simplicity of your code but there are a few downsides too
(please correct me if I've got something wrong - btw the documentation of high-level design would be welcome so that one doesn't have to understand that from the patches):

Rate-limiting design uses per-task delay injection when controller sees that this
task or cgroup have done too much IO. This is similar to balance_dirty_pages but this approach extends this logic to any kind of IO and doesn't require special
point where task checks balance because delay ejected in task-work which runs when task returns into userspace.

Quoted 7 lines

1) The bandwidth controller simply accounts number of bytes submitted for IO in submit_bio(). This doesn't reflect HW capabilities in any way. There a huge difference between a process submitting single block random IO and a process doing the same amount of sequential IO. This could be somewhat dealt with by not accounting number of bytes but rather time it took to complete a bio (but that somewhat complicates the code and block layer already does similar counting so it would be good you used that).

Yes, it is. But completion time works accurately only for simple disks with single
depth queue. For disk with NCQ completion time often have no relation to actual complexity of requests.

We could use it as third metric in addition to bandwidth and iops or combine all of them into some abstract disk utilization, anyway splitting accounting and scheduling phases gives more flexibility.

Quoted 4 lines

2) The controller accounts bio to current task - that makes the limiting useless for background writeback. You need to somehow propagate i_memcg into submit_bio() so that IO is properly accounted.

It would be nice to get information about randomness of issued writeback but I think propagation disk bandwidth limit and especially iops limit into writeback
is almost useless, we must ratelimit task which generates data flow before it generations next bunch of dirty memory.

In some cases it's possible to slowdown writeback but journalled filesystems often require write everything to close transaction.

Quoted 8 lines

3) The controller doesn't seem to guarantee any quality of service at the IO level like blkcg does. The controller limits amount of IO userspace is able to submit to kernel but only after we decide to submit the IO to disk. So at that time cgroup may have generated lots of IO - e.g. by dirtying lots of pages - and there's nothing to protect other cgroups from starvation because of writeback of these pages.

Quoted 4 lines

Especially the last point seems to be essential to your approach (although you could somewhat mitigate the issue by accounting the IO already when it is entering the kernel) and I'm not sure whether that's really acceptable for potential users of this feature.

I try to limit amount of dirty memory and speed of generation new dirty pages after crossing threshold. Probably it's also possible to limit speed of switching
pages from "dirty" to "towrite" state. Thus memcg could have a lot of dirty pages
but couldn't trigger immediate writeback for all of them.

I think it's possible to build solid io scheduler using that approach:
this controller proves only single static limits for bandwidth and iops, but they
might be balanced automatically depending on disk speed and estimated load.

Quoted 44 lines

Honza

Quoted 42 lines

[1] [PATCH RFC] fsio: filesystem io accounting cgroup
http://marc.info/?l=linux-kernel&m=137331569501655&w=2

Patches also available here:
https://github.com/koct9i/linux.git branch memcg_dirty_control

---

Konstantin Khebnikov (6):
memcg: inode-based dirty and writeback pages accounting
memcg: dirty-set limiting and filtered writeback
memcg: track shared inodes with dirty pages
percpu_ratelimit: high-performance ratelimiting counter
delay-injection: resource management via procrastination
memcg: filesystem bandwidth controller

 block/blk-core.c                 |    2
 fs/direct-io.c                   |    2
 fs/fs-writeback.c                |   22 ++
 fs/inode.c                       |    1
 include/linux/backing-dev.h      |    1
 include/linux/fs.h               |   14 +
 include/linux/memcontrol.h       |   27 +++
 include/linux/percpu_ratelimit.h |   45 ++++
 include/linux/sched.h            |    7 +
 include/linux/writeback.h        |    1
 include/trace/events/sched.h     |    7 +
 include/trace/events/writeback.h |    1
 kernel/sched/core.c              |   66 +++++++
 kernel/sched/fair.c              |   12 +
 lib/Makefile                     |    1
 lib/percpu_ratelimit.c           |  168 +++++++++++++++++
 mm/memcontrol.c                  |  381 ++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c              |   32 +++
 mm/readahead.c                   |    2
 mm/truncate.c                    |    1
 mm/vmscan.c                      |    4
 21 files changed, 787 insertions(+), 10 deletions(-)
 create mode 100644 include/linux/percpu_ratelimit.h
 create mode 100644 lib/percpu_ratelimit.c
Quoted 5 lines
Quoted 2 lines

--
Signature

--
Jan Kara <[email protected]>
SUSE Labs, CR

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#12 lore raw
Quoted 43 lines · Konstantin Khlebnikov
Quoted 21 lines · Jan Kara

Hello,

Quoted 18 lines · Konstantin Khebnikov

This is ressurection of my old RFC patch for dirty-set accounting cgroup [1] Now it's merged into memory cgroup and got bandwidth controller as a bonus.

That shows alternative solution: less accurate but much less monstrous than accurate page-based dirty-set controller from Tejun Heo.

Memory overhead: +1 pointer into struct address_space. Perfomance overhead is almost zero, no new locks added.

Idea is stright forward: link each inode to some cgroup when first dirty page appers and account all dirty pages to it. Writeback is implemented as single per-bdi writeback work which writes only inodes which belong to memory cgroups where amount of dirty memory is beyond thresholds.

Third patch adds trick for handling shared inodes which have dirty pages from several cgroups: it marks whole inode as shared and alters writeback filter for it.

Quoted 6 lines
Quoted 2 lines

The rest is an example of bandwith and iops controller build on top of that. Design is completely original, I bet nobody ever used task-works for that =)

So I like the simplicity of your code but there are a few downsides too
(please correct me if I've got something wrong - btw the documentation of high-level design would be welcome so that one doesn't have to understand that from the patches):

Rate-limiting design uses per-task delay injection when controller sees that this task or cgroup have done too much IO. This is similar to balance_dirty_pages but this approach extends this logic to any kind of IO and doesn't require special point where task checks balance because delay ejected in task-work which runs when task returns into userspace.

Quoted 7 lines

1) The bandwidth controller simply accounts number of bytes submitted for IO in submit_bio(). This doesn't reflect HW capabilities in any way. There a huge difference between a process submitting single block random IO and a process doing the same amount of sequential IO. This could be somewhat dealt with by not accounting number of bytes but rather time it took to complete a bio (but that somewhat complicates the code and block layer already does similar counting so it would be good you used that).

Quoted 3 lines

Yes, it is. But completion time works accurately only for simple disks with single depth queue. For disk with NCQ completion time often have no relation to actual complexity of requests.

Well, in the same way I could say that the number bytes submitted has
often no relation to the actual complexity of requests. :) But the fact that there are more IO requests running in parallel is the reason why blkcg code allows only requests from a single blkcg to run in the HW so that we know to whom the time should be accounted. Of course, this means a non-trivial overhead when switching between time slices for different blkcgs so total system throughput is affected. So either approach has its pros and cons.

Quoted 3 lines

We could use it as third metric in addition to bandwidth and iops or combine all of them into some abstract disk utilization, anyway splitting accounting and scheduling phases gives more flexibility.

Yes, I agree that splitting accounting and scheduling of pauses gives
more flexibility.

Quoted 8 lines
Quoted 3 lines

2) The controller accounts bio to current task - that makes the limiting useless for background writeback. You need to somehow propagate i_memcg into submit_bio() so that IO is properly accounted.

It would be nice to get information about randomness of issued writeback but I think propagation disk bandwidth limit and especially iops limit into writeback is almost useless, we must ratelimit task which generates data flow before it generations next bunch of dirty memory.

I think we misunderstand here. The point I was trying to make was that
I don't see how accounting of writes works with your patches. But after reading your patches again I have notice that you end up inserting delay from balance_dirty_pages() which is the point I originally missed.

Quoted 2 lines

In some cases it's possible to slowdown writeback but journalled filesystems often require write everything to close transaction.

Well, yes, that's a concern mostly for metadata (which e.g. Tejun keeps
unaccounted) but ext4 can have this problem for data in some cases as well.

Quoted 12 lines
Quoted 11 lines

3) The controller doesn't seem to guarantee any quality of service at the IO level like blkcg does. The controller limits amount of IO userspace is able to submit to kernel but only after we decide to submit the IO to disk. So at that time cgroup may have generated lots of IO - e.g. by dirtying lots of pages - and there's nothing to protect other cgroups from starvation because of writeback of these pages.

Especially the last point seems to be essential to your approach (although you could somewhat mitigate the issue by accounting the IO already when it is entering the kernel) and I'm not sure whether that's really acceptable for potential users of this feature.

Quoted 5 lines

I try to limit amount of dirty memory and speed of generation new dirty pages after crossing threshold. Probably it's also possible to limit speed of switching pages from "dirty" to "towrite" state. Thus memcg could have a lot of dirty pages but couldn't trigger immediate writeback for all of them.

Well, with your way of throttling there's also a possibility to submit
e.g. a large batch of AIO reads or AIO writes and the process will get blocked only after all the IO is submitted and the system is hogged. So IMHO it's hard to guarantee anything in the small scale with your patches.

Quoted 4 lines

I think it's possible to build solid io scheduler using that approach:
this controller proves only single static limits for bandwidth and iops, but they might be balanced automatically depending on disk speed and estimated load.

Honza

Signature · 8 lines
Jan Kara <[email protected]>
SUSE Labs, CR

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
maintainer
#13 lore raw
In reply to Konstantin Khebnikov

Hello, Konstantin.

Sorry about the delay.

Quoted 5 lines · Konstantin Khebnikov

This is ressurection of my old RFC patch for dirty-set accounting cgroup [1] Now it's merged into memory cgroup and got bandwidth controller as a bonus.

That shows alternative solution: less accurate but much less monstrous than accurate page-based dirty-set controller from Tejun Heo.

I went over the whole patchset and ISTR having reviewed this a while ago and the conclusion is the same. This appears to be simpler on the surface but this is a hackjob of a design to put it nicely. You're working around the complexity of pressure propagation from the lower layer by building a separate pressure layer at the top most layer. In doing so, it's duplicating what already exist below in degenerate forms but at the cost of fundamental crippling of the whole thing.

This, even in its current simplistic form, is already a dead end. e.g. iops or bw aren't even the proper resources to distribute for rotating disks, IO time is, which is what a large proportion of cfq is trying to estimate and distribute. What if there are multiple filesystems on a single device? Or if a cgroup accesses multiple backing devices? How would you guarantee low latency access to a high priority cgroup while a huge inode from a low pri cgroup is being written out when none of the lower layers have any idea what they're doing?

Sure, these issues can be dealt with partially with various workarounds and additions and I'm sure we'll be doing that if we go down this path, but the only thing that'll lead to is duplicating more of what's already in the lower layers with ever growing list of behavioral and interface oddities which are inherent to the design.

Even in the absence of alternatives, I'd be strongly against this direction. I think this sort of ad-hoc "let's solve this one immediate issue in the easiest way possible" is often worse than not doing anything. In the longer term, things like this paint us into a corner of which we can't easily get out and memcg happens to be an area where that sort of things took place quite a bit in the past and people have been desparately trying to right the course, so, no, I don't think this is happening.

I agree that propagating backpressure from the lower layer involves more complexity but it is a full and conceptually and design-wise straight-forward solution which doesn't need to get constantly papered over. This is the right thing to do. It can be argued that the amount of complexity can be reduced by tracking dirty pages per-inode, but, if we're gonna do that, we should converting memcg itself to be per address space too. The arguments would be exactly the same for memcg and memcg and writeback must be on the same foundation.

Thanks.

Signature · 7 lines
tejun

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>