diff options
Diffstat (limited to 'mm/memcontrol.c')
| -rw-r--r-- | mm/memcontrol.c | 354 | 
1 files changed, 255 insertions, 99 deletions
diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 2bd3df3d101a..e6f0d5ef320a 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -1034,13 +1034,13 @@ static unsigned long mem_cgroup_margin(struct mem_cgroup *memcg)  	unsigned long limit;  	count = page_counter_read(&memcg->memory); -	limit = READ_ONCE(memcg->memory.limit); +	limit = READ_ONCE(memcg->memory.max);  	if (count < limit)  		margin = limit - count;  	if (do_memsw_account()) {  		count = page_counter_read(&memcg->memsw); -		limit = READ_ONCE(memcg->memsw.limit); +		limit = READ_ONCE(memcg->memsw.max);  		if (count <= limit)  			margin = min(margin, limit - count);  		else @@ -1148,13 +1148,13 @@ void mem_cgroup_print_oom_info(struct mem_cgroup *memcg, struct task_struct *p)  	pr_info("memory: usage %llukB, limit %llukB, failcnt %lu\n",  		K((u64)page_counter_read(&memcg->memory)), -		K((u64)memcg->memory.limit), memcg->memory.failcnt); +		K((u64)memcg->memory.max), memcg->memory.failcnt);  	pr_info("memory+swap: usage %llukB, limit %llukB, failcnt %lu\n",  		K((u64)page_counter_read(&memcg->memsw)), -		K((u64)memcg->memsw.limit), memcg->memsw.failcnt); +		K((u64)memcg->memsw.max), memcg->memsw.failcnt);  	pr_info("kmem: usage %llukB, limit %llukB, failcnt %lu\n",  		K((u64)page_counter_read(&memcg->kmem)), -		K((u64)memcg->kmem.limit), memcg->kmem.failcnt); +		K((u64)memcg->kmem.max), memcg->kmem.failcnt);  	for_each_mem_cgroup_tree(iter, memcg) {  		pr_info("Memory cgroup stats for "); @@ -1179,21 +1179,21 @@ void mem_cgroup_print_oom_info(struct mem_cgroup *memcg, struct task_struct *p)  /*   * Return the memory (and swap, if configured) limit for a memcg.   */ -unsigned long mem_cgroup_get_limit(struct mem_cgroup *memcg) +unsigned long mem_cgroup_get_max(struct mem_cgroup *memcg)  { -	unsigned long limit; +	unsigned long max; -	limit = memcg->memory.limit; +	max = memcg->memory.max;  	if (mem_cgroup_swappiness(memcg)) { -		unsigned long memsw_limit; -		unsigned long swap_limit; +		unsigned long memsw_max; +		unsigned long swap_max; -		memsw_limit = memcg->memsw.limit; -		swap_limit = memcg->swap.limit; -		swap_limit = min(swap_limit, (unsigned long)total_swap_pages); -		limit = min(limit + swap_limit, memsw_limit); +		memsw_max = memcg->memsw.max; +		swap_max = memcg->swap.max; +		swap_max = min(swap_max, (unsigned long)total_swap_pages); +		max = min(max + swap_max, memsw_max);  	} -	return limit; +	return max;  }  static bool mem_cgroup_out_of_memory(struct mem_cgroup *memcg, gfp_t gfp_mask, @@ -2444,12 +2444,13 @@ static inline int mem_cgroup_move_swap_account(swp_entry_t entry,  }  #endif -static DEFINE_MUTEX(memcg_limit_mutex); +static DEFINE_MUTEX(memcg_max_mutex); -static int mem_cgroup_resize_limit(struct mem_cgroup *memcg, -				   unsigned long limit, bool memsw) +static int mem_cgroup_resize_max(struct mem_cgroup *memcg, +				 unsigned long max, bool memsw)  {  	bool enlarge = false; +	bool drained = false;  	int ret;  	bool limits_invariant;  	struct page_counter *counter = memsw ? &memcg->memsw : &memcg->memory; @@ -2460,26 +2461,32 @@ static int mem_cgroup_resize_limit(struct mem_cgroup *memcg,  			break;  		} -		mutex_lock(&memcg_limit_mutex); +		mutex_lock(&memcg_max_mutex);  		/*  		 * Make sure that the new limit (memsw or memory limit) doesn't -		 * break our basic invariant rule memory.limit <= memsw.limit. +		 * break our basic invariant rule memory.max <= memsw.max.  		 */ -		limits_invariant = memsw ? limit >= memcg->memory.limit : -					   limit <= memcg->memsw.limit; +		limits_invariant = memsw ? max >= memcg->memory.max : +					   max <= memcg->memsw.max;  		if (!limits_invariant) { -			mutex_unlock(&memcg_limit_mutex); +			mutex_unlock(&memcg_max_mutex);  			ret = -EINVAL;  			break;  		} -		if (limit > counter->limit) +		if (max > counter->max)  			enlarge = true; -		ret = page_counter_limit(counter, limit); -		mutex_unlock(&memcg_limit_mutex); +		ret = page_counter_set_max(counter, max); +		mutex_unlock(&memcg_max_mutex);  		if (!ret)  			break; +		if (!drained) { +			drain_all_stock(memcg); +			drained = true; +			continue; +		} +  		if (!try_to_free_mem_cgroup_pages(memcg, 1,  					GFP_KERNEL, !memsw)) {  			ret = -EBUSY; @@ -2603,6 +2610,9 @@ static int mem_cgroup_force_empty(struct mem_cgroup *memcg)  	/* we call try-to-free pages for make this cgroup empty */  	lru_add_drain_all(); + +	drain_all_stock(memcg); +  	/* try to free all pages in this cgroup */  	while (nr_retries && page_counter_read(&memcg->memory)) {  		int progress; @@ -2757,7 +2767,7 @@ static u64 mem_cgroup_read_u64(struct cgroup_subsys_state *css,  			return (u64)mem_cgroup_usage(memcg, true) * PAGE_SIZE;  		return (u64)page_counter_read(counter) * PAGE_SIZE;  	case RES_LIMIT: -		return (u64)counter->limit * PAGE_SIZE; +		return (u64)counter->max * PAGE_SIZE;  	case RES_MAX_USAGE:  		return (u64)counter->watermark * PAGE_SIZE;  	case RES_FAILCNT: @@ -2871,24 +2881,24 @@ static void memcg_free_kmem(struct mem_cgroup *memcg)  }  #endif /* !CONFIG_SLOB */ -static int memcg_update_kmem_limit(struct mem_cgroup *memcg, -				   unsigned long limit) +static int memcg_update_kmem_max(struct mem_cgroup *memcg, +				 unsigned long max)  {  	int ret; -	mutex_lock(&memcg_limit_mutex); -	ret = page_counter_limit(&memcg->kmem, limit); -	mutex_unlock(&memcg_limit_mutex); +	mutex_lock(&memcg_max_mutex); +	ret = page_counter_set_max(&memcg->kmem, max); +	mutex_unlock(&memcg_max_mutex);  	return ret;  } -static int memcg_update_tcp_limit(struct mem_cgroup *memcg, unsigned long limit) +static int memcg_update_tcp_max(struct mem_cgroup *memcg, unsigned long max)  {  	int ret; -	mutex_lock(&memcg_limit_mutex); +	mutex_lock(&memcg_max_mutex); -	ret = page_counter_limit(&memcg->tcpmem, limit); +	ret = page_counter_set_max(&memcg->tcpmem, max);  	if (ret)  		goto out; @@ -2913,7 +2923,7 @@ static int memcg_update_tcp_limit(struct mem_cgroup *memcg, unsigned long limit)  		memcg->tcpmem_active = true;  	}  out: -	mutex_unlock(&memcg_limit_mutex); +	mutex_unlock(&memcg_max_mutex);  	return ret;  } @@ -2941,16 +2951,16 @@ static ssize_t mem_cgroup_write(struct kernfs_open_file *of,  		}  		switch (MEMFILE_TYPE(of_cft(of)->private)) {  		case _MEM: -			ret = mem_cgroup_resize_limit(memcg, nr_pages, false); +			ret = mem_cgroup_resize_max(memcg, nr_pages, false);  			break;  		case _MEMSWAP: -			ret = mem_cgroup_resize_limit(memcg, nr_pages, true); +			ret = mem_cgroup_resize_max(memcg, nr_pages, true);  			break;  		case _KMEM: -			ret = memcg_update_kmem_limit(memcg, nr_pages); +			ret = memcg_update_kmem_max(memcg, nr_pages);  			break;  		case _TCP: -			ret = memcg_update_tcp_limit(memcg, nr_pages); +			ret = memcg_update_tcp_max(memcg, nr_pages);  			break;  		}  		break; @@ -3083,7 +3093,7 @@ static int memcg_numa_stat_show(struct seq_file *m, void *v)  #endif /* CONFIG_NUMA */  /* Universal VM events cgroup1 shows, original sort order */ -unsigned int memcg1_events[] = { +static const unsigned int memcg1_events[] = {  	PGPGIN,  	PGPGOUT,  	PGFAULT, @@ -3126,8 +3136,8 @@ static int memcg_stat_show(struct seq_file *m, void *v)  	/* Hierarchical information */  	memory = memsw = PAGE_COUNTER_MAX;  	for (mi = memcg; mi; mi = parent_mem_cgroup(mi)) { -		memory = min(memory, mi->memory.limit); -		memsw = min(memsw, mi->memsw.limit); +		memory = min(memory, mi->memory.max); +		memsw = min(memsw, mi->memsw.max);  	}  	seq_printf(m, "hierarchical_memory_limit %llu\n",  		   (u64)memory * PAGE_SIZE); @@ -3540,7 +3550,8 @@ static int mem_cgroup_oom_control_read(struct seq_file *sf, void *v)  	seq_printf(sf, "oom_kill_disable %d\n", memcg->oom_kill_disable);  	seq_printf(sf, "under_oom %d\n", (bool)memcg->under_oom); -	seq_printf(sf, "oom_kill %lu\n", memcg_sum_events(memcg, OOM_KILL)); +	seq_printf(sf, "oom_kill %lu\n", +		   atomic_long_read(&memcg->memory_events[MEMCG_OOM_KILL]));  	return 0;  } @@ -3562,11 +3573,6 @@ static int mem_cgroup_oom_control_write(struct cgroup_subsys_state *css,  #ifdef CONFIG_CGROUP_WRITEBACK -struct list_head *mem_cgroup_cgwb_list(struct mem_cgroup *memcg) -{ -	return &memcg->cgwb_list; -} -  static int memcg_wb_domain_init(struct mem_cgroup *memcg, gfp_t gfp)  {  	return wb_domain_init(&memcg->cgwb_domain, gfp); @@ -3626,7 +3632,7 @@ void mem_cgroup_wb_stats(struct bdi_writeback *wb, unsigned long *pfilepages,  	*pheadroom = PAGE_COUNTER_MAX;  	while ((parent = parent_mem_cgroup(memcg))) { -		unsigned long ceiling = min(memcg->memory.limit, memcg->high); +		unsigned long ceiling = min(memcg->memory.max, memcg->high);  		unsigned long used = page_counter_read(&memcg->memory);  		*pheadroom = min(*pheadroom, ceiling - min(ceiling, used)); @@ -3849,7 +3855,7 @@ static ssize_t memcg_write_event_control(struct kernfs_open_file *of,  	if (ret)  		goto out_put_css; -	efile.file->f_op->poll(efile.file, &event->pt); +	vfs_poll(efile.file, &event->pt);  	spin_lock(&memcg->event_list_lock);  	list_add(&event->list, &memcg->event_list); @@ -4270,7 +4276,8 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)  	}  	spin_unlock(&memcg->event_list_lock); -	memcg->low = 0; +	page_counter_set_min(&memcg->memory, 0); +	page_counter_set_low(&memcg->memory, 0);  	memcg_offline_kmem(memcg);  	wb_memcg_offline(memcg); @@ -4319,12 +4326,13 @@ static void mem_cgroup_css_reset(struct cgroup_subsys_state *css)  {  	struct mem_cgroup *memcg = mem_cgroup_from_css(css); -	page_counter_limit(&memcg->memory, PAGE_COUNTER_MAX); -	page_counter_limit(&memcg->swap, PAGE_COUNTER_MAX); -	page_counter_limit(&memcg->memsw, PAGE_COUNTER_MAX); -	page_counter_limit(&memcg->kmem, PAGE_COUNTER_MAX); -	page_counter_limit(&memcg->tcpmem, PAGE_COUNTER_MAX); -	memcg->low = 0; +	page_counter_set_max(&memcg->memory, PAGE_COUNTER_MAX); +	page_counter_set_max(&memcg->swap, PAGE_COUNTER_MAX); +	page_counter_set_max(&memcg->memsw, PAGE_COUNTER_MAX); +	page_counter_set_max(&memcg->kmem, PAGE_COUNTER_MAX); +	page_counter_set_max(&memcg->tcpmem, PAGE_COUNTER_MAX); +	page_counter_set_min(&memcg->memory, 0); +	page_counter_set_low(&memcg->memory, 0);  	memcg->high = PAGE_COUNTER_MAX;  	memcg->soft_limit = PAGE_COUNTER_MAX;  	memcg_wb_domain_size_changed(memcg); @@ -5061,10 +5069,40 @@ static u64 memory_current_read(struct cgroup_subsys_state *css,  	return (u64)page_counter_read(&memcg->memory) * PAGE_SIZE;  } +static int memory_min_show(struct seq_file *m, void *v) +{ +	struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); +	unsigned long min = READ_ONCE(memcg->memory.min); + +	if (min == PAGE_COUNTER_MAX) +		seq_puts(m, "max\n"); +	else +		seq_printf(m, "%llu\n", (u64)min * PAGE_SIZE); + +	return 0; +} + +static ssize_t memory_min_write(struct kernfs_open_file *of, +				char *buf, size_t nbytes, loff_t off) +{ +	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of)); +	unsigned long min; +	int err; + +	buf = strstrip(buf); +	err = page_counter_memparse(buf, "max", &min); +	if (err) +		return err; + +	page_counter_set_min(&memcg->memory, min); + +	return nbytes; +} +  static int memory_low_show(struct seq_file *m, void *v)  {  	struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); -	unsigned long low = READ_ONCE(memcg->low); +	unsigned long low = READ_ONCE(memcg->memory.low);  	if (low == PAGE_COUNTER_MAX)  		seq_puts(m, "max\n"); @@ -5086,7 +5124,7 @@ static ssize_t memory_low_write(struct kernfs_open_file *of,  	if (err)  		return err; -	memcg->low = low; +	page_counter_set_low(&memcg->memory, low);  	return nbytes;  } @@ -5131,7 +5169,7 @@ static ssize_t memory_high_write(struct kernfs_open_file *of,  static int memory_max_show(struct seq_file *m, void *v)  {  	struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); -	unsigned long max = READ_ONCE(memcg->memory.limit); +	unsigned long max = READ_ONCE(memcg->memory.max);  	if (max == PAGE_COUNTER_MAX)  		seq_puts(m, "max\n"); @@ -5155,7 +5193,7 @@ static ssize_t memory_max_write(struct kernfs_open_file *of,  	if (err)  		return err; -	xchg(&memcg->memory.limit, max); +	xchg(&memcg->memory.max, max);  	for (;;) {  		unsigned long nr_pages = page_counter_read(&memcg->memory); @@ -5202,7 +5240,8 @@ static int memory_events_show(struct seq_file *m, void *v)  		   atomic_long_read(&memcg->memory_events[MEMCG_MAX]));  	seq_printf(m, "oom %lu\n",  		   atomic_long_read(&memcg->memory_events[MEMCG_OOM])); -	seq_printf(m, "oom_kill %lu\n", memcg_sum_events(memcg, OOM_KILL)); +	seq_printf(m, "oom_kill %lu\n", +		   atomic_long_read(&memcg->memory_events[MEMCG_OOM_KILL]));  	return 0;  } @@ -5296,6 +5335,12 @@ static struct cftype memory_files[] = {  		.read_u64 = memory_current_read,  	},  	{ +		.name = "min", +		.flags = CFTYPE_NOT_ON_ROOT, +		.seq_show = memory_min_show, +		.write = memory_min_write, +	}, +	{  		.name = "low",  		.flags = CFTYPE_NOT_ON_ROOT,  		.seq_show = memory_low_show, @@ -5344,54 +5389,144 @@ struct cgroup_subsys memory_cgrp_subsys = {  };  /** - * mem_cgroup_low - check if memory consumption is below the normal range + * mem_cgroup_protected - check if memory consumption is in the normal range   * @root: the top ancestor of the sub-tree being checked   * @memcg: the memory cgroup to check   * - * Returns %true if memory consumption of @memcg, and that of all - * ancestors up to (but not including) @root, is below the normal range. + * WARNING: This function is not stateless! It can only be used as part + *          of a top-down tree iteration, not for isolated queries. + * + * Returns one of the following: + *   MEMCG_PROT_NONE: cgroup memory is not protected + *   MEMCG_PROT_LOW: cgroup memory is protected as long there is + *     an unprotected supply of reclaimable memory from other cgroups. + *   MEMCG_PROT_MIN: cgroup memory is protected + * + * @root is exclusive; it is never protected when looked at directly   * - * @root is exclusive; it is never low when looked at directly and isn't - * checked when traversing the hierarchy. + * To provide a proper hierarchical behavior, effective memory.min/low values + * are used. Below is the description of how effective memory.low is calculated. + * Effective memory.min values is calculated in the same way.   * - * Excluding @root enables using memory.low to prioritize memory usage - * between cgroups within a subtree of the hierarchy that is limited by - * memory.high or memory.max. + * Effective memory.low is always equal or less than the original memory.low. + * If there is no memory.low overcommittment (which is always true for + * top-level memory cgroups), these two values are equal. + * Otherwise, it's a part of parent's effective memory.low, + * calculated as a cgroup's memory.low usage divided by sum of sibling's + * memory.low usages, where memory.low usage is the size of actually + * protected memory.   * - * For example, given cgroup A with children B and C: + *                                             low_usage + * elow = min( memory.low, parent->elow * ------------------ ), + *                                        siblings_low_usage   * - *    A - *   / \ - *  B   C + *             | memory.current, if memory.current < memory.low + * low_usage = | +	       | 0, otherwise.   * - * and   * - *  1. A/memory.current > A/memory.high - *  2. A/B/memory.current < A/B/memory.low - *  3. A/C/memory.current >= A/C/memory.low + * Such definition of the effective memory.low provides the expected + * hierarchical behavior: parent's memory.low value is limiting + * children, unprotected memory is reclaimed first and cgroups, + * which are not using their guarantee do not affect actual memory + * distribution.   * - * As 'A' is high, i.e. triggers reclaim from 'A', and 'B' is low, we - * should reclaim from 'C' until 'A' is no longer high or until we can - * no longer reclaim from 'C'.  If 'A', i.e. @root, isn't excluded by - * mem_cgroup_low when reclaming from 'A', then 'B' won't be considered - * low and we will reclaim indiscriminately from both 'B' and 'C'. + * For example, if there are memcgs A, A/B, A/C, A/D and A/E: + * + *     A      A/memory.low = 2G, A/memory.current = 6G + *    //\\ + *   BC  DE   B/memory.low = 3G  B/memory.current = 2G + *            C/memory.low = 1G  C/memory.current = 2G + *            D/memory.low = 0   D/memory.current = 2G + *            E/memory.low = 10G E/memory.current = 0 + * + * and the memory pressure is applied, the following memory distribution + * is expected (approximately): + * + *     A/memory.current = 2G + * + *     B/memory.current = 1.3G + *     C/memory.current = 0.6G + *     D/memory.current = 0 + *     E/memory.current = 0 + * + * These calculations require constant tracking of the actual low usages + * (see propagate_protected_usage()), as well as recursive calculation of + * effective memory.low values. But as we do call mem_cgroup_protected() + * path for each memory cgroup top-down from the reclaim, + * it's possible to optimize this part, and save calculated elow + * for next usage. This part is intentionally racy, but it's ok, + * as memory.low is a best-effort mechanism.   */ -bool mem_cgroup_low(struct mem_cgroup *root, struct mem_cgroup *memcg) +enum mem_cgroup_protection mem_cgroup_protected(struct mem_cgroup *root, +						struct mem_cgroup *memcg)  { +	struct mem_cgroup *parent; +	unsigned long emin, parent_emin; +	unsigned long elow, parent_elow; +	unsigned long usage; +  	if (mem_cgroup_disabled()) -		return false; +		return MEMCG_PROT_NONE;  	if (!root)  		root = root_mem_cgroup;  	if (memcg == root) -		return false; +		return MEMCG_PROT_NONE; + +	usage = page_counter_read(&memcg->memory); +	if (!usage) +		return MEMCG_PROT_NONE; -	for (; memcg != root; memcg = parent_mem_cgroup(memcg)) { -		if (page_counter_read(&memcg->memory) >= memcg->low) -			return false; +	emin = memcg->memory.min; +	elow = memcg->memory.low; + +	parent = parent_mem_cgroup(memcg); +	/* No parent means a non-hierarchical mode on v1 memcg */ +	if (!parent) +		return MEMCG_PROT_NONE; + +	if (parent == root) +		goto exit; + +	parent_emin = READ_ONCE(parent->memory.emin); +	emin = min(emin, parent_emin); +	if (emin && parent_emin) { +		unsigned long min_usage, siblings_min_usage; + +		min_usage = min(usage, memcg->memory.min); +		siblings_min_usage = atomic_long_read( +			&parent->memory.children_min_usage); + +		if (min_usage && siblings_min_usage) +			emin = min(emin, parent_emin * min_usage / +				   siblings_min_usage);  	} -	return true; +	parent_elow = READ_ONCE(parent->memory.elow); +	elow = min(elow, parent_elow); +	if (elow && parent_elow) { +		unsigned long low_usage, siblings_low_usage; + +		low_usage = min(usage, memcg->memory.low); +		siblings_low_usage = atomic_long_read( +			&parent->memory.children_low_usage); + +		if (low_usage && siblings_low_usage) +			elow = min(elow, parent_elow * low_usage / +				   siblings_low_usage); +	} + +exit: +	memcg->memory.emin = emin; +	memcg->memory.elow = elow; + +	if (usage <= emin) +		return MEMCG_PROT_MIN; +	else if (usage <= elow) +		return MEMCG_PROT_LOW; +	else +		return MEMCG_PROT_NONE;  }  /** @@ -6012,10 +6147,17 @@ int mem_cgroup_try_charge_swap(struct page *page, swp_entry_t entry)  	if (!memcg)  		return 0; +	if (!entry.val) { +		memcg_memory_event(memcg, MEMCG_SWAP_FAIL); +		return 0; +	} +  	memcg = mem_cgroup_id_get_online(memcg);  	if (!mem_cgroup_is_root(memcg) &&  	    !page_counter_try_charge(&memcg->swap, nr_pages, &counter)) { +		memcg_memory_event(memcg, MEMCG_SWAP_MAX); +		memcg_memory_event(memcg, MEMCG_SWAP_FAIL);  		mem_cgroup_id_put(memcg);  		return -ENOMEM;  	} @@ -6067,7 +6209,7 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)  		return nr_swap_pages;  	for (; memcg != root_mem_cgroup; memcg = parent_mem_cgroup(memcg))  		nr_swap_pages = min_t(long, nr_swap_pages, -				      READ_ONCE(memcg->swap.limit) - +				      READ_ONCE(memcg->swap.max) -  				      page_counter_read(&memcg->swap));  	return nr_swap_pages;  } @@ -6088,7 +6230,7 @@ bool mem_cgroup_swap_full(struct page *page)  		return false;  	for (; memcg != root_mem_cgroup; memcg = parent_mem_cgroup(memcg)) -		if (page_counter_read(&memcg->swap) * 2 >= memcg->swap.limit) +		if (page_counter_read(&memcg->swap) * 2 >= memcg->swap.max)  			return true;  	return false; @@ -6122,7 +6264,7 @@ static u64 swap_current_read(struct cgroup_subsys_state *css,  static int swap_max_show(struct seq_file *m, void *v)  {  	struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); -	unsigned long max = READ_ONCE(memcg->swap.limit); +	unsigned long max = READ_ONCE(memcg->swap.max);  	if (max == PAGE_COUNTER_MAX)  		seq_puts(m, "max\n"); @@ -6144,15 +6286,23 @@ static ssize_t swap_max_write(struct kernfs_open_file *of,  	if (err)  		return err; -	mutex_lock(&memcg_limit_mutex); -	err = page_counter_limit(&memcg->swap, max); -	mutex_unlock(&memcg_limit_mutex); -	if (err) -		return err; +	xchg(&memcg->swap.max, max);  	return nbytes;  } +static int swap_events_show(struct seq_file *m, void *v) +{ +	struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); + +	seq_printf(m, "max %lu\n", +		   atomic_long_read(&memcg->memory_events[MEMCG_SWAP_MAX])); +	seq_printf(m, "fail %lu\n", +		   atomic_long_read(&memcg->memory_events[MEMCG_SWAP_FAIL])); + +	return 0; +} +  static struct cftype swap_files[] = {  	{  		.name = "swap.current", @@ -6165,6 +6315,12 @@ static struct cftype swap_files[] = {  		.seq_show = swap_max_show,  		.write = swap_max_write,  	}, +	{ +		.name = "swap.events", +		.flags = CFTYPE_NOT_ON_ROOT, +		.file_offset = offsetof(struct mem_cgroup, swap_events_file), +		.seq_show = swap_events_show, +	},  	{ }	/* terminate */  };  |