memcg: change try_to_free_pages to hierarchical_reclaim
[sfrench/cifs-2.6.git] / mm / memcontrol.c
index b9cd57b667d612ad9a98bcdb44ef80044653dcd2..a7ecf23150c50b88168f8514fbeb3d0766303168 100644 (file)
@@ -51,6 +51,7 @@ static int really_do_swap_account __initdata = 1; /* for remember boot option*/
 #define do_swap_account                (0)
 #endif
 
+static DEFINE_MUTEX(memcg_tasklist);   /* can be hold under cgroup_mutex */
 
 /*
  * Statistics for memory cgroup.
@@ -103,6 +104,8 @@ struct mem_cgroup_per_zone {
         */
        struct list_head        lists[NR_LRU_LISTS];
        unsigned long           count[NR_LRU_LISTS];
+
+       struct zone_reclaim_stat reclaim_stat;
 };
 /* Macro for accessing counter */
 #define MEM_CGROUP_ZSTAT(mz, idx)      ((mz)->count[(idx)])
@@ -142,6 +145,11 @@ struct mem_cgroup {
         */
        struct mem_cgroup_lru_info info;
 
+       /*
+         protect against reclaim related member.
+       */
+       spinlock_t reclaim_param_lock;
+
        int     prev_priority;  /* for recording reclaim priority */
 
        /*
@@ -156,6 +164,9 @@ struct mem_cgroup {
        unsigned long   last_oom_jiffies;
        int             obsolete;
        atomic_t        refcnt;
+
+       unsigned int    swappiness;
+
        /*
         * statistics. This must be placed at the end of memcg.
         */
@@ -183,7 +194,6 @@ pcg_default_flags[NR_CHARGE_TYPE] = {
        0, /* FORCE */
 };
 
-
 /* for encoding cft->private value on file */
 #define _MEM                   (0)
 #define _MEMSWAP               (1)
@@ -231,6 +241,9 @@ page_cgroup_zoneinfo(struct page_cgroup *pc)
        int nid = page_cgroup_nid(pc);
        int zid = page_cgroup_zid(pc);
 
+       if (!mem)
+               return NULL;
+
        return mem_cgroup_zoneinfo(mem, nid, zid);
 }
 
@@ -393,39 +406,108 @@ int mem_cgroup_calc_mapped_ratio(struct mem_cgroup *mem)
  */
 int mem_cgroup_get_reclaim_priority(struct mem_cgroup *mem)
 {
-       return mem->prev_priority;
+       int prev_priority;
+
+       spin_lock(&mem->reclaim_param_lock);
+       prev_priority = mem->prev_priority;
+       spin_unlock(&mem->reclaim_param_lock);
+
+       return prev_priority;
 }
 
 void mem_cgroup_note_reclaim_priority(struct mem_cgroup *mem, int priority)
 {
+       spin_lock(&mem->reclaim_param_lock);
        if (priority < mem->prev_priority)
                mem->prev_priority = priority;
+       spin_unlock(&mem->reclaim_param_lock);
 }
 
 void mem_cgroup_record_reclaim_priority(struct mem_cgroup *mem, int priority)
 {
+       spin_lock(&mem->reclaim_param_lock);
        mem->prev_priority = priority;
+       spin_unlock(&mem->reclaim_param_lock);
 }
 
-/*
- * Calculate # of pages to be scanned in this priority/zone.
- * See also vmscan.c
- *
- * priority starts from "DEF_PRIORITY" and decremented in each loop.
- * (see include/linux/mmzone.h)
- */
+static int calc_inactive_ratio(struct mem_cgroup *memcg, unsigned long *present_pages)
+{
+       unsigned long active;
+       unsigned long inactive;
+       unsigned long gb;
+       unsigned long inactive_ratio;
+
+       inactive = mem_cgroup_get_all_zonestat(memcg, LRU_INACTIVE_ANON);
+       active = mem_cgroup_get_all_zonestat(memcg, LRU_ACTIVE_ANON);
 
-long mem_cgroup_calc_reclaim(struct mem_cgroup *mem, struct zone *zone,
-                                       int priority, enum lru_list lru)
+       gb = (inactive + active) >> (30 - PAGE_SHIFT);
+       if (gb)
+               inactive_ratio = int_sqrt(10 * gb);
+       else
+               inactive_ratio = 1;
+
+       if (present_pages) {
+               present_pages[0] = inactive;
+               present_pages[1] = active;
+       }
+
+       return inactive_ratio;
+}
+
+int mem_cgroup_inactive_anon_is_low(struct mem_cgroup *memcg)
+{
+       unsigned long active;
+       unsigned long inactive;
+       unsigned long present_pages[2];
+       unsigned long inactive_ratio;
+
+       inactive_ratio = calc_inactive_ratio(memcg, present_pages);
+
+       inactive = present_pages[0];
+       active = present_pages[1];
+
+       if (inactive * inactive_ratio < active)
+               return 1;
+
+       return 0;
+}
+
+unsigned long mem_cgroup_zone_nr_pages(struct mem_cgroup *memcg,
+                                      struct zone *zone,
+                                      enum lru_list lru)
 {
-       long nr_pages;
        int nid = zone->zone_pgdat->node_id;
        int zid = zone_idx(zone);
-       struct mem_cgroup_per_zone *mz = mem_cgroup_zoneinfo(mem, nid, zid);
+       struct mem_cgroup_per_zone *mz = mem_cgroup_zoneinfo(memcg, nid, zid);
 
-       nr_pages = MEM_CGROUP_ZSTAT(mz, lru);
+       return MEM_CGROUP_ZSTAT(mz, lru);
+}
 
-       return (nr_pages >> priority);
+struct zone_reclaim_stat *mem_cgroup_get_reclaim_stat(struct mem_cgroup *memcg,
+                                                     struct zone *zone)
+{
+       int nid = zone->zone_pgdat->node_id;
+       int zid = zone_idx(zone);
+       struct mem_cgroup_per_zone *mz = mem_cgroup_zoneinfo(memcg, nid, zid);
+
+       return &mz->reclaim_stat;
+}
+
+struct zone_reclaim_stat *
+mem_cgroup_get_reclaim_stat_from_page(struct page *page)
+{
+       struct page_cgroup *pc;
+       struct mem_cgroup_per_zone *mz;
+
+       if (mem_cgroup_disabled())
+               return NULL;
+
+       pc = lookup_page_cgroup(page);
+       mz = page_cgroup_zoneinfo(pc);
+       if (!mz)
+               return NULL;
+
+       return &mz->reclaim_stat;
 }
 
 unsigned long mem_cgroup_isolate_pages(unsigned long nr_to_scan,
@@ -571,6 +653,34 @@ done:
        return ret;
 }
 
+static bool mem_cgroup_check_under_limit(struct mem_cgroup *mem)
+{
+       if (do_swap_account) {
+               if (res_counter_check_under_limit(&mem->res) &&
+                       res_counter_check_under_limit(&mem->memsw))
+                       return true;
+       } else
+               if (res_counter_check_under_limit(&mem->res))
+                       return true;
+       return false;
+}
+
+static unsigned int get_swappiness(struct mem_cgroup *memcg)
+{
+       struct cgroup *cgrp = memcg->css.cgroup;
+       unsigned int swappiness;
+
+       /* root ? */
+       if (cgrp->parent == NULL)
+               return vm_swappiness;
+
+       spin_lock(&memcg->reclaim_param_lock);
+       swappiness = memcg->swappiness;
+       spin_unlock(&memcg->reclaim_param_lock);
+
+       return swappiness;
+}
+
 /*
  * Dance down the hierarchy if needed to reclaim memory. We remember the
  * last child we reclaimed from, so that we don't end up penalizing
@@ -591,9 +701,12 @@ static int mem_cgroup_hierarchical_reclaim(struct mem_cgroup *root_mem,
         * but there might be left over accounting, even after children
         * have left.
         */
-       ret = try_to_free_mem_cgroup_pages(root_mem, gfp_mask, noswap);
-       if (res_counter_check_under_limit(&root_mem->res))
+       ret = try_to_free_mem_cgroup_pages(root_mem, gfp_mask, noswap,
+                                          get_swappiness(root_mem));
+       if (mem_cgroup_check_under_limit(root_mem))
                return 0;
+       if (!root_mem->use_hierarchy)
+               return ret;
 
        next_mem = mem_cgroup_get_first_node(root_mem);
 
@@ -605,8 +718,9 @@ static int mem_cgroup_hierarchical_reclaim(struct mem_cgroup *root_mem,
                        cgroup_unlock();
                        continue;
                }
-               ret = try_to_free_mem_cgroup_pages(next_mem, gfp_mask, noswap);
-               if (res_counter_check_under_limit(&root_mem->res))
+               ret = try_to_free_mem_cgroup_pages(next_mem, gfp_mask, noswap,
+                                                  get_swappiness(next_mem));
+               if (mem_cgroup_check_under_limit(root_mem))
                        return 0;
                cgroup_lock();
                next_mem = mem_cgroup_get_next_node(next_mem, root_mem);
@@ -709,16 +823,14 @@ static int __mem_cgroup_try_charge(struct mm_struct *mm,
                 * current usage of the cgroup before giving up
                 *
                 */
-               if (do_swap_account) {
-                       if (res_counter_check_under_limit(&mem_over_limit->res) &&
-                           res_counter_check_under_limit(&mem_over_limit->memsw))
-                               continue;
-               } else if (res_counter_check_under_limit(&mem_over_limit->res))
-                               continue;
+               if (mem_cgroup_check_under_limit(mem_over_limit))
+                       continue;
 
                if (!nr_retries--) {
                        if (oom) {
+                               mutex_lock(&memcg_tasklist);
                                mem_cgroup_out_of_memory(mem_over_limit, gfp_mask);
+                               mutex_unlock(&memcg_tasklist);
                                mem_over_limit->last_oom_jiffies = jiffies;
                        }
                        goto nomem;
@@ -730,27 +842,8 @@ nomem:
        return -ENOMEM;
 }
 
-/**
- * mem_cgroup_try_charge - get charge of PAGE_SIZE.
- * @mm: an mm_struct which is charged against. (when *memcg is NULL)
- * @gfp_mask: gfp_mask for reclaim.
- * @memcg: a pointer to memory cgroup which is charged against.
- *
- * charge against memory cgroup pointed by *memcg. if *memcg == NULL, estimated
- * memory cgroup from @mm is got and stored in *memcg.
- *
- * Returns 0 if success. -ENOMEM at failure.
- * This call can invoke OOM-Killer.
- */
-
-int mem_cgroup_try_charge(struct mm_struct *mm,
-                         gfp_t mask, struct mem_cgroup **memcg)
-{
-       return __mem_cgroup_try_charge(mm, mask, memcg, true);
-}
-
 /*
- * commit a charge got by mem_cgroup_try_charge() and makes page_cgroup to be
+ * commit a charge got by __mem_cgroup_try_charge() and makes page_cgroup to be
  * USED state. If already USED, uncharge and return.
  */
 
@@ -1163,7 +1256,9 @@ __mem_cgroup_uncharge_common(struct page *page, enum charge_type ctype)
        mz = page_cgroup_zoneinfo(pc);
        unlock_page_cgroup(pc);
 
-       css_put(&mem->css);
+       /* at swapout, this memcg will be accessed to record to swap */
+       if (ctype != MEM_CGROUP_CHARGE_TYPE_SWAPOUT)
+               css_put(&mem->css);
 
        return mem;
 
@@ -1204,6 +1299,8 @@ void mem_cgroup_uncharge_swapcache(struct page *page, swp_entry_t ent)
                swap_cgroup_record(ent, memcg);
                mem_cgroup_get(memcg);
        }
+       if (memcg)
+               css_put(&memcg->css);
 }
 
 #ifdef CONFIG_CGROUP_MEM_RES_CTLR_SWAP
@@ -1248,7 +1345,7 @@ int mem_cgroup_prepare_migration(struct page *page, struct mem_cgroup **ptr)
        unlock_page_cgroup(pc);
 
        if (mem) {
-               ret = mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem);
+               ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem, false);
                css_put(&mem->css);
        }
        *ptr = mem;
@@ -1333,8 +1430,8 @@ int mem_cgroup_shrink_usage(struct mm_struct *mm, gfp_t gfp_mask)
        rcu_read_unlock();
 
        do {
-               progress = try_to_free_mem_cgroup_pages(mem, gfp_mask, true);
-               progress += res_counter_check_under_limit(&mem->res);
+               progress = mem_cgroup_hierarchical_reclaim(mem, gfp_mask, true);
+               progress += mem_cgroup_check_under_limit(mem);
        } while (!progress && --retry);
 
        css_put(&mem->css);
@@ -1377,10 +1474,11 @@ static int mem_cgroup_resize_limit(struct mem_cgroup *memcg,
                if (!ret)
                        break;
 
-               progress = try_to_free_mem_cgroup_pages(memcg,
-                               GFP_KERNEL, false);
+               progress = mem_cgroup_hierarchical_reclaim(memcg, GFP_KERNEL,
+                                                          false);
                if (!progress)                  retry_count--;
        }
+
        return ret;
 }
 
@@ -1418,7 +1516,7 @@ int mem_cgroup_resize_memsw_limit(struct mem_cgroup *memcg,
                        break;
 
                oldusage = res_counter_read_u64(&memcg->memsw, RES_USAGE);
-               try_to_free_mem_cgroup_pages(memcg, GFP_KERNEL, true);
+               mem_cgroup_hierarchical_reclaim(memcg, GFP_KERNEL, true);
                curusage = res_counter_read_u64(&memcg->memsw, RES_USAGE);
                if (curusage >= oldusage)
                        retry_count--;
@@ -1549,8 +1647,8 @@ try_to_free:
                        ret = -EINTR;
                        goto out;
                }
-               progress = try_to_free_mem_cgroup_pages(mem,
-                                                 GFP_KERNEL, false);
+               progress = try_to_free_mem_cgroup_pages(mem, GFP_KERNEL,
+                                               false, get_swappiness(mem));
                if (!progress) {
                        nr_retries--;
                        /* maybe some writeback is necessary */
@@ -1664,6 +1762,34 @@ static int mem_cgroup_write(struct cgroup *cont, struct cftype *cft,
        return ret;
 }
 
+static void memcg_get_hierarchical_limit(struct mem_cgroup *memcg,
+               unsigned long long *mem_limit, unsigned long long *memsw_limit)
+{
+       struct cgroup *cgroup;
+       unsigned long long min_limit, min_memsw_limit, tmp;
+
+       min_limit = res_counter_read_u64(&memcg->res, RES_LIMIT);
+       min_memsw_limit = res_counter_read_u64(&memcg->memsw, RES_LIMIT);
+       cgroup = memcg->css.cgroup;
+       if (!memcg->use_hierarchy)
+               goto out;
+
+       while (cgroup->parent) {
+               cgroup = cgroup->parent;
+               memcg = mem_cgroup_from_cont(cgroup);
+               if (!memcg->use_hierarchy)
+                       break;
+               tmp = res_counter_read_u64(&memcg->res, RES_LIMIT);
+               min_limit = min(min_limit, tmp);
+               tmp = res_counter_read_u64(&memcg->memsw, RES_LIMIT);
+               min_memsw_limit = min(min_memsw_limit, tmp);
+       }
+out:
+       *mem_limit = min_limit;
+       *memsw_limit = min_memsw_limit;
+       return;
+}
+
 static int mem_cgroup_reset(struct cgroup *cont, unsigned int event)
 {
        struct mem_cgroup *mem;
@@ -1737,6 +1863,74 @@ static int mem_control_stat_show(struct cgroup *cont, struct cftype *cft,
                cb->fill(cb, "unevictable", unevictable * PAGE_SIZE);
 
        }
+       {
+               unsigned long long limit, memsw_limit;
+               memcg_get_hierarchical_limit(mem_cont, &limit, &memsw_limit);
+               cb->fill(cb, "hierarchical_memory_limit", limit);
+               if (do_swap_account)
+                       cb->fill(cb, "hierarchical_memsw_limit", memsw_limit);
+       }
+
+#ifdef CONFIG_DEBUG_VM
+       cb->fill(cb, "inactive_ratio", calc_inactive_ratio(mem_cont, NULL));
+
+       {
+               int nid, zid;
+               struct mem_cgroup_per_zone *mz;
+               unsigned long recent_rotated[2] = {0, 0};
+               unsigned long recent_scanned[2] = {0, 0};
+
+               for_each_online_node(nid)
+                       for (zid = 0; zid < MAX_NR_ZONES; zid++) {
+                               mz = mem_cgroup_zoneinfo(mem_cont, nid, zid);
+
+                               recent_rotated[0] +=
+                                       mz->reclaim_stat.recent_rotated[0];
+                               recent_rotated[1] +=
+                                       mz->reclaim_stat.recent_rotated[1];
+                               recent_scanned[0] +=
+                                       mz->reclaim_stat.recent_scanned[0];
+                               recent_scanned[1] +=
+                                       mz->reclaim_stat.recent_scanned[1];
+                       }
+               cb->fill(cb, "recent_rotated_anon", recent_rotated[0]);
+               cb->fill(cb, "recent_rotated_file", recent_rotated[1]);
+               cb->fill(cb, "recent_scanned_anon", recent_scanned[0]);
+               cb->fill(cb, "recent_scanned_file", recent_scanned[1]);
+       }
+#endif
+
+       return 0;
+}
+
+static u64 mem_cgroup_swappiness_read(struct cgroup *cgrp, struct cftype *cft)
+{
+       struct mem_cgroup *memcg = mem_cgroup_from_cont(cgrp);
+
+       return get_swappiness(memcg);
+}
+
+static int mem_cgroup_swappiness_write(struct cgroup *cgrp, struct cftype *cft,
+                                      u64 val)
+{
+       struct mem_cgroup *memcg = mem_cgroup_from_cont(cgrp);
+       struct mem_cgroup *parent;
+       if (val > 100)
+               return -EINVAL;
+
+       if (cgrp->parent == NULL)
+               return -EINVAL;
+
+       parent = mem_cgroup_from_cont(cgrp->parent);
+       /* If under hierarchy, only empty-root can set this value */
+       if ((parent->use_hierarchy) ||
+           (memcg->use_hierarchy && !list_empty(&cgrp->children)))
+               return -EINVAL;
+
+       spin_lock(&memcg->reclaim_param_lock);
+       memcg->swappiness = val;
+       spin_unlock(&memcg->reclaim_param_lock);
+
        return 0;
 }
 
@@ -1778,6 +1972,11 @@ static struct cftype mem_cgroup_files[] = {
                .write_u64 = mem_cgroup_hierarchy_write,
                .read_u64 = mem_cgroup_hierarchy_read,
        },
+       {
+               .name = "swappiness",
+               .read_u64 = mem_cgroup_swappiness_read,
+               .write_u64 = mem_cgroup_swappiness_write,
+       },
 };
 
 #ifdef CONFIG_CGROUP_MEM_RES_CTLR_SWAP
@@ -1965,8 +2164,11 @@ mem_cgroup_create(struct cgroup_subsys *ss, struct cgroup *cont)
                res_counter_init(&mem->res, NULL);
                res_counter_init(&mem->memsw, NULL);
        }
-
        mem->last_scanned_child = NULL;
+       spin_lock_init(&mem->reclaim_param_lock);
+
+       if (parent)
+               mem->swappiness = get_swappiness(parent);
 
        return &mem->css;
 free_out:
@@ -2008,25 +2210,12 @@ static void mem_cgroup_move_task(struct cgroup_subsys *ss,
                                struct cgroup *old_cont,
                                struct task_struct *p)
 {
-       struct mm_struct *mm;
-       struct mem_cgroup *mem, *old_mem;
-
-       mm = get_task_mm(p);
-       if (mm == NULL)
-               return;
-
-       mem = mem_cgroup_from_cont(cont);
-       old_mem = mem_cgroup_from_cont(old_cont);
-
+       mutex_lock(&memcg_tasklist);
        /*
-        * Only thread group leaders are allowed to migrate, the mm_struct is
-        * in effect owned by the leader
+        * FIXME: It's better to move charges of this process from old
+        * memcg to new memcg. But it's just on TODO-List now.
         */
-       if (!thread_group_leader(p))
-               goto out;
-
-out:
-       mmput(mm);
+       mutex_unlock(&memcg_tasklist);
 }
 
 struct cgroup_subsys mem_cgroup_subsys = {