zswap_shrinker_count() scales the number of freeable entries on a memcg's zswap LRU by the memcg's compression ratio. That compression ratio is computed from the memcg's MEMCG_ZSWAP_B and MEMCG_ZSWAPPED stats. However, for the root memcg, the zswap entries owned by the root memcg itself are never accounted for in the stats (obj_cgroup_charge_zswap() skips the root objcg, and before the root memcg had an objcg, its entries weren't charged at all). The stats only reflect the entries owned by the root memcg's descendants. When the root memcg's descendants have no zswap entries (eg when the whole workload runs in the root cgroup), the shrinker reports nothing to reclaim for the root memcg, and its zswap entries are never written back by the shrinker. When its descendants do have entries, the root memcg's entries are scaled by the descendants' compression ratio instead of a ratio that includes them. Fix this by using the zswap pool-wide counters for the root memcg instead. The zswap pool-wide counters cover every zswap entry, including those owned by the root memcg. This also gets rid of having to flush the root memcg's stats, which flushes every cgroup in the system. In a VM running a reclaim-heavy workload in the root cgroup with the zswap shrinker enabled, the shrinker wrote back no pages before this change (zswpwb stayed at 0) and 1016 pages with it. Fixes: b5ba474f3f51 ("zswap: shrink zswap pool based on memory pressure") Acked-by: Yosry Ahmed Reviewed-by: SJ Park Acked-by: Nhat Pham Reviewed-by: Johannes Weiner Signed-off-by: Joanne Koong --- mm/zswap.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/zswap.c b/mm/zswap.c index a2e0314db1cf..6e1cd3e482f7 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -1330,9 +1330,12 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, * runtime-disabled: we can get sc->memcg == NULL, which is ok * for the lruvec, but not for memcg_page_state(). * - * Without memcg, use the zswap pool-wide metrics. + * Without memcg, or for the root memcg, use the zswap pool-wide + * metrics. The root memcg's stats don't count the entries it owns + * itself (obj_cgroup_charge_zswap() skips the root objcg), whereas the + * pool-wide metrics cover every entry and don't need a flush. */ - if (!mem_cgroup_disabled()) { + if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) { mem_cgroup_flush_stats_ratelimited(memcg); nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); -- 2.52.1