History

mm: memcontrol: make cgroup stats and events query API explicitly local

Patch series "mm: memcontrol: memory.stat cost & correctness". The cgroup memory.stat file holds recursive statistics for the entire subtree. The current implementation does this tree walk on-demand whenever the file is read. This is giving us problems in production. 1. The cost of aggregating the statistics on-demand is high. A lot of system service cgroups are mostly idle and their stats don't change between reads, yet we always have to check them. There are also always some lazily-dying cgroups sitting around that are pinned by a handful of remaining page cache; the same applies to them. In an application that periodically monitors memory.stat in our fleet, we have seen the aggregation consume up to 5% CPU time. 2. When cgroups die and disappear from the cgroup tree, so do their accumulated vm events. The result is that the event counters at higher-level cgroups can go backwards and confuse some of our automation, let alone people looking at the graphs over time. To address both issues, this patch series changes the stat implementation to spill counts upwards when the counters change. The upward spilling is batched using the existing per-cpu cache. In a sparse file stress test with 5 level cgroup nesting, the additional cost of the flushing was negligible (a little under 1% of CPU at 100% CPU utilization, compared to the 5% of reading memory.stat during regular operation). This patch (of 4): memcg_page_state(), lruvec_page_state(), memcg_sum_events() are currently returning the state of the local memcg or lruvec, not the recursive state. In practice there is a demand for both versions, although the callers that want the recursive counts currently sum them up by hand. Per default, cgroups are considered recursive entities and generally we expect more users of the recursive counters, with the local counts being special cases. To reflect that in the name, add a _local suffix to the current implementations. The following patch will re-incarnate these functions with recursive semantics, but with an O(1) implementation. [hannes@cmpxchg.org: fix bisection hole] Link: http://lkml.kernel.org/r/20190417160347.GC23013@cmpxchg.org Link: http://lkml.kernel.org/r/20190412151507.2769-2-hannes@cmpxchg.org Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Reviewed-by: Shakeel Butt <shakeelb@google.com> Reviewed-by: Roman Gushchin <guro@fb.com> Cc: Michal Hocko <mhocko@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Johannes Weiner committed 7 years ago

Commit 205b20c1 parent 6a024334 files · +36 −33Browse files

Unified · Split

4 files
  1. include/linux/memcontrol.h +8 −8
  2. mm/memcontrol.c +21 −19
  3. mm/vmscan.c +3 −3
  4. mm/workingset.c +4 −3
include/linux/memcontrol.h+8−8
@@ -554,8 +554,8 @@ void unlock_page_memcg(struct page *page); · 553 unmodified lines
554554 * idx can be of type enum memcg_stat_item or node_stat_item.
555555 * Keep in sync with memcg_exact_page_state().
556556 */
557−static inline unsigned long memcg_page_state(struct mem_cgroup *memcg,
558− int idx)
557+static inline unsigned long memcg_page_state_local(struct mem_cgroup *memcg,
558+ int idx)
559559 {
560560 long x = atomic_long_read(&memcg->vmstats[idx]);
561561 #ifdef CONFIG_SMP
@@ -624,8 +624,8 @@ static inline void mod_memcg_page_state(struct page *page, · 62 unmodified lines
624624 mod_memcg_state(page->mem_cgroup, idx, val);
625625 }
626626
627−static inline unsigned long lruvec_page_state(struct lruvec *lruvec,
628− enum node_stat_item idx)
627+static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,
628+ enum node_stat_item idx)
629629 {
630630 struct mem_cgroup_per_node *pn;
631631 long x;
@@ -1011,8 +1011,8 @@ static inline void mem_cgroup_print_oom_group(struct mem_cgroup *memcg) · 379 unmodified lines
10111011 {
10121012 }
10131013
1014−static inline unsigned long memcg_page_state(struct mem_cgroup *memcg,
1015− int idx)
1014+static inline unsigned long memcg_page_state_local(struct mem_cgroup *memcg,
1015+ int idx)
10161016 {
10171017 return 0;
10181018 }
@@ -1041,8 +1041,8 @@ static inline void mod_memcg_page_state(struct page *page, · 22 unmodified lines
10411041 {
10421042 }
10431043
1044−static inline unsigned long lruvec_page_state(struct lruvec *lruvec,
1045− enum node_stat_item idx)
1044+static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,
1045+ enum node_stat_item idx)
10461046 {
10471047 return node_page_state(lruvec_pgdat(lruvec), idx);
10481048 }
mm/memcontrol.c+21−19
@@ -687,8 +687,8 @@ mem_cgroup_largest_soft_limit_node(struct mem_cgroup_tree_per_node *mctz) · 686 unmodified lines
687687 return mz;
688688 }
689689
690−static unsigned long memcg_sum_events(struct mem_cgroup *memcg,
691− int event)
690+static unsigned long memcg_events_local(struct mem_cgroup *memcg,
691+ int event)
692692 {
693693 return atomic_long_read(&memcg->vmevents[event]);
694694 }
@@ -1325,12 +1325,14 @@ void mem_cgroup_print_oom_meminfo(struct mem_cgroup *memcg) · 630 unmodified lines
13251325 if (memcg1_stats[i] == MEMCG_SWAP && !do_swap_account)
13261326 continue;
13271327 pr_cont(" %s:%luKB", memcg1_stat_names[i],
1328− K(memcg_page_state(iter, memcg1_stats[i])));
1328+ K(memcg_page_state_local(iter,
1329+ memcg1_stats[i])));
13291330 }
13301331
13311332 for (i = 0; i < NR_LRU_LISTS; i++)
13321333 pr_cont(" %s:%luKB", mem_cgroup_lru_names[i],
1333− K(memcg_page_state(iter, NR_LRU_BASE + i)));
1334+ K(memcg_page_state_local(iter,
1335+ NR_LRU_BASE + i)));
13341336
13351337 pr_cont("\n");
13361338 }
@@ -1396,13 +1398,13 @@ static bool test_mem_cgroup_node_reclaimable(struct mem_cgroup *memcg, · 59 unmodified lines
13961398 {
13971399 struct lruvec *lruvec = mem_cgroup_lruvec(NODE_DATA(nid), memcg);
13981400
1399− if (lruvec_page_state(lruvec, NR_INACTIVE_FILE) ||
1400− lruvec_page_state(lruvec, NR_ACTIVE_FILE))
1401+ if (lruvec_page_state_local(lruvec, NR_INACTIVE_FILE) ||
1402+ lruvec_page_state_local(lruvec, NR_ACTIVE_FILE))
14011403 return true;
14021404 if (noswap || !total_swap_pages)
14031405 return false;
1404− if (lruvec_page_state(lruvec, NR_INACTIVE_ANON) ||
1405− lruvec_page_state(lruvec, NR_ACTIVE_ANON))
1406+ if (lruvec_page_state_local(lruvec, NR_INACTIVE_ANON) ||
1407+ lruvec_page_state_local(lruvec, NR_ACTIVE_ANON))
14061408 return true;
14071409 return false;
14081410
@@ -2961,16 +2963,16 @@ static void accumulate_vmstats(struct mem_cgroup *memcg, · 1552 unmodified lines
29612963
29622964 for_each_mem_cgroup_tree(mi, memcg) {
29632965 for (i = 0; i < acc->vmstats_size; i++)
2964− acc->vmstats[i] += memcg_page_state(mi,
2966+ acc->vmstats[i] += memcg_page_state_local(mi,
29652967 acc->vmstats_array ? acc->vmstats_array[i] : i);
29662968
29672969 for (i = 0; i < acc->vmevents_size; i++)
2968− acc->vmevents[i] += memcg_sum_events(mi,
2970+ acc->vmevents[i] += memcg_events_local(mi,
29692971 acc->vmevents_array
29702972 ? acc->vmevents_array[i] : i);
29712973
29722974 for (i = 0; i < NR_LRU_LISTS; i++)
2973− acc->lru_pages[i] += memcg_page_state(mi,
2975+ acc->lru_pages[i] += memcg_page_state_local(mi,
29742976 NR_LRU_BASE + i);
29752977 }
29762978 }
@@ -2983,10 +2985,10 @@ static unsigned long mem_cgroup_usage(struct mem_cgroup *memcg, bool swap) · 6 unmodified lines
29832985 struct mem_cgroup *iter;
29842986
29852987 for_each_mem_cgroup_tree(iter, memcg) {
2986− val += memcg_page_state(iter, MEMCG_CACHE);
2987− val += memcg_page_state(iter, MEMCG_RSS);
2988+ val += memcg_page_state_local(iter, MEMCG_CACHE);
2989+ val += memcg_page_state_local(iter, MEMCG_RSS);
29882990 if (swap)
2989− val += memcg_page_state(iter, MEMCG_SWAP);
2991+ val += memcg_page_state_local(iter, MEMCG_SWAP);
29902992 }
29912993 } else {
29922994 if (!swap)
@@ -3328,7 +3330,7 @@ static unsigned long mem_cgroup_node_nr_lru_pages(struct mem_cgroup *memcg, · 335 unmodified lines
33283330 for_each_lru(lru) {
33293331 if (!(BIT(lru) & lru_mask))
33303332 continue;
3331− nr += lruvec_page_state(lruvec, NR_LRU_BASE + lru);
3333+ nr += lruvec_page_state_local(lruvec, NR_LRU_BASE + lru);
33323334 }
33333335 return nr;
33343336 }
@@ -3342,7 +3344,7 @@ static unsigned long mem_cgroup_nr_lru_pages(struct mem_cgroup *memcg, · 7 unmodified lines
33423344 for_each_lru(lru) {
33433345 if (!(BIT(lru) & lru_mask))
33443346 continue;
3345− nr += memcg_page_state(memcg, NR_LRU_BASE + lru);
3347+ nr += memcg_page_state_local(memcg, NR_LRU_BASE + lru);
33463348 }
33473349 return nr;
33483350 }
@@ -3427,17 +3429,17 @@ static int memcg_stat_show(struct seq_file *m, void *v) · 78 unmodified lines
34273429 if (memcg1_stats[i] == MEMCG_SWAP && !do_memsw_account())
34283430 continue;
34293431 seq_printf(m, "%s %lu\n", memcg1_stat_names[i],
3430− memcg_page_state(memcg, memcg1_stats[i]) *
3432+ memcg_page_state_local(memcg, memcg1_stats[i]) *
34313433 PAGE_SIZE);
34323434 }
34333435
34343436 for (i = 0; i < ARRAY_SIZE(memcg1_events); i++)
34353437 seq_printf(m, "%s %lu\n", memcg1_event_names[i],
3436− memcg_sum_events(memcg, memcg1_events[i]));
3438+ memcg_events_local(memcg, memcg1_events[i]));
34373439
34383440 for (i = 0; i < NR_LRU_LISTS; i++)
34393441 seq_printf(m, "%s %lu\n", mem_cgroup_lru_names[i],
3440− memcg_page_state(memcg, NR_LRU_BASE + i) *
3442+ memcg_page_state_local(memcg, NR_LRU_BASE + i) *
34413443 PAGE_SIZE);
34423444
34433445 /* Hierarchical information */
mm/vmscan.c+3−3
@@ -346,7 +346,7 @@ unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone · 345 unmodified lines
346346 int zid;
347347
348348 if (!mem_cgroup_disabled())
349− lru_size = lruvec_page_state(lruvec, NR_LRU_BASE + lru);
349+ lru_size = lruvec_page_state_local(lruvec, NR_LRU_BASE + lru);
350350 else
351351 lru_size = node_page_state(lruvec_pgdat(lruvec), NR_LRU_BASE + lru);
352352
@@ -2150,7 +2150,7 @@ static bool inactive_list_is_low(struct lruvec *lruvec, bool file, · 1797 unmodified lines
21502150 * is being established. Disable active list protection to get
21512151 * rid of the stale workingset quickly.
21522152 */
2153− refaults = lruvec_page_state(lruvec, WORKINGSET_ACTIVATE);
2153+ refaults = lruvec_page_state_local(lruvec, WORKINGSET_ACTIVATE);
21542154 if (file && actual_reclaim && lruvec->refaults != refaults) {
21552155 inactive_ratio = 0;
21562156 } else {
@@ -2912,7 +2912,7 @@ static void snapshot_refaults(struct mem_cgroup *root_memcg, pg_data_t *pgdat) · 755 unmodified lines
29122912 struct lruvec *lruvec;
29132913
29142914 lruvec = mem_cgroup_lruvec(pgdat, memcg);
2915− refaults = lruvec_page_state(lruvec, WORKINGSET_ACTIVATE);
2915+ refaults = lruvec_page_state_local(lruvec, WORKINGSET_ACTIVATE);
29162916 lruvec->refaults = refaults;
29172917 } while ((memcg = mem_cgroup_iter(root_memcg, memcg, NULL)));
29182918 }
mm/workingset.c+4−3
@@ -430,9 +430,10 @@ static unsigned long count_shadow_nodes(struct shrinker *shrinker, · 429 unmodified lines
430430
431431 lruvec = mem_cgroup_lruvec(NODE_DATA(sc->nid), sc->memcg);
432432 for (pages = 0, i = 0; i < NR_LRU_LISTS; i++)
433− pages += lruvec_page_state(lruvec, NR_LRU_BASE + i);
434− pages += lruvec_page_state(lruvec, NR_SLAB_RECLAIMABLE);
435− pages += lruvec_page_state(lruvec, NR_SLAB_UNRECLAIMABLE);
433+ pages += lruvec_page_state_local(lruvec,
434+ NR_LRU_BASE + i);
435+ pages += lruvec_page_state_local(lruvec, NR_SLAB_RECLAIMABLE);
436+ pages += lruvec_page_state_local(lruvec, NR_SLAB_UNRECLAIMABLE);
436437 } else
437438 #endif
438439 pages = node_present_pages(sc->nid);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t