Skip to contentSkip to conversation

mm: zswap: add per-memcg stat for incompressible pages

PATCH Superseded Jiayuan Chen 5 participants 15 messages Feb 5 – Feb 6

Subsystems (4)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 mm: zswap: add per-memcg stat for incompressible pages

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

  $ cgcreate -g memory:test
  $ cgexec -g memory:test ./test_zswpraw &
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 0
  zswpout 0
  zswpwb 0
  $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 104800256
  zswpin 0
  zswpout 51222
  zswpwb 0
  $ pkill test_zswpraw
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 1
  zswpout 51222
  zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h      | 9 +++++++++
 mm/memcontrol.c            | 6 ++++++
 mm/zswap.c                 | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
include/linux/memcontrol.h +1 -0
index b6c82c8f73e1..83d1328f81d1 100644
@@ -39,6 +39,7 @@ enum memcg_stat_item {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,
 	MEMCG_NR_STAT,
 };
 
include/linux/zswap.h +9 -0
index 30c193a1207e..94f84b154b71 100644
@@ -7,6 +7,15 @@
 
 struct lruvec;
 
+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+	return size == PAGE_SIZE;
+}
+
 extern atomic_long_t zswap_stored_pages;
 
 #ifdef CONFIG_ZSWAP
mm/memcontrol.c +6 -0
index 007413a53b45..32fb801530a3 100644
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,
 };
 
 #define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items)
@@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = {
 #ifdef CONFIG_ZSWAP
 	{ "zswap",			MEMCG_ZSWAP_B			},
 	{ "zswapped",			MEMCG_ZSWAPPED			},
+	{ "zswpraw",			MEMCG_ZSWAP_RAW			},
 #endif
 	{ "file_mapped",		NR_FILE_MAPPED			},
 	{ "file_dirty",			NR_FILE_DIRTY			},
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);
 	rcu_read_unlock();
 }
 
@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
 	rcu_read_unlock();
 }
 
mm/zswap.c +3 -3
index 3d2d59ac3f9c..54ab4d126f64 100644
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry)
 		obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
 		obj_cgroup_put(entry->objcg);
 	}
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_dec(&zswap_stored_incompressible_pages);
 	zswap_entry_cache_free(entry);
 	atomic_long_dec(&zswap_stored_pages);
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
 	zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);
 
 	/* zswap entries of length PAGE_SIZE are not compressed. */
-	if (entry->length == PAGE_SIZE) {
+	if (zswap_is_raw(entry->length)) {
 		WARN_ON_ONCE(input->length != PAGE_SIZE);
 		memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE);
 		dlen = PAGE_SIZE;
@@ -1448,7 +1448,7 @@ static bool zswap_store_page(struct page *page,
 		obj_cgroup_charge_zswap(objcg, entry->length);
 	}
 	atomic_long_inc(&zswap_stored_pages);
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_inc(&zswap_stored_incompressible_pages);
 
 	/*
Signature · 1 line
2.43.0
maintainer Acked-by on v1 1/1
#1 lore raw
Quoted 21 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

Quoted 8 lines

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

I personally agree. This is especially useful for multi-tenants setups, where different workloads can have different compressibility, which can muddy the waters, and might prefer different swapping treatment (disk swapping, zswapping, zswap + disk swap through zswap shrinker). It might also give us data to extend zswap (zswap compressibility-based rejection, or different compression levels).

Naming is a bit off though, but I'm not a native English speaker :) I think Chris Li pointed the out the necessity of per-memcg counters too:

https://lore.kernel.org/linux-mm/CAF8kJuONDFj4NAksaR4j_WyDbNwNGYLmTe-o76rqU17La=nkOw@mail.gmail.com/

Can you add this to the patch changelog in later versions? :)

+ Chris Li

Quoted 39 lines

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

  $ cgcreate -g memory:test
  $ cgexec -g memory:test ./test_zswpraw &
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 0
  zswpout 0
  zswpwb 0
  $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 104800256
  zswpin 0
  zswpout 51222
  zswpwb 0
Quoted 8 lines
  $ pkill test_zswpraw
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 1
  zswpout 51222
  zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Would be nice if some versions of this can be turned into a selftest :)

Instead of reading zstd data, you can read from /dev/urandom. I found those to be incompressible, usually.

Feel free to send this as a follow-up patch, but would love to see it :)

Quoted 46 lines
---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h      | 9 +++++++++
 mm/memcontrol.c            | 6 ++++++
 mm/zswap.c                 | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
include/linux/memcontrol.h +1 -0
index b6c82c8f73e1..83d1328f81d1 100644
@@ -39,6 +39,7 @@ enum memcg_stat_item {
        MEMCG_KMEM,
        MEMCG_ZSWAP_B,
        MEMCG_ZSWAPPED,
+       MEMCG_ZSWAP_RAW,
        MEMCG_NR_STAT,
 };
 
include/linux/zswap.h +9 -0
index 30c193a1207e..94f84b154b71 100644
@@ -7,6 +7,15 @@
 
 struct lruvec;
 
+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+       return size == PAGE_SIZE;
+}
+
 extern atomic_long_t zswap_stored_pages;
 
 #ifdef CONFIG_ZSWAP
mm/memcontrol.c +0 -0
index 007413a53b45..32fb801530a3 100644

mm/memcontrol.c static const unsigned int memcg_stat_items[] = { · line 344

Quoted 4 lines
mm/memcontrol.c +1 -0
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = {
        MEMCG_KMEM,
        MEMCG_ZSWAP_B,
        MEMCG_ZSWAPPED,
+       MEMCG_ZSWAP_RAW,

not sure how I feel about the naming, but I don't have a recommendation :)

Quoted 61 lines
mm/memcontrol.c +5 -0
 };
 
 #define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items)
@@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = {
 #ifdef CONFIG_ZSWAP
        { "zswap",                      MEMCG_ZSWAP_B                   },
        { "zswapped",                   MEMCG_ZSWAPPED                  },
+       { "zswpraw",                    MEMCG_ZSWAP_RAW                 },
 #endif
        { "file_mapped",                NR_FILE_MAPPED                  },
        { "file_dirty",                 NR_FILE_DIRTY                   },
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)
        memcg = obj_cgroup_memcg(objcg);
        mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
        mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+       if (zswap_is_raw(size))
+               mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);
        rcu_read_unlock();
 }
 
@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
        memcg = obj_cgroup_memcg(objcg);
        mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
        mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+       if (zswap_is_raw(size))
+               mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
        rcu_read_unlock();
 }
 
mm/zswap.c +3 -4
index 3d2d59ac3f9c..54ab4d126f64 100644
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry)
                obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
                obj_cgroup_put(entry->objcg);
        }
-       if (entry->length == PAGE_SIZE)
+       if (zswap_is_raw(entry->length))
                atomic_long_dec(&zswap_stored_incompressible_pages);
        zswap_entry_cache_free(entry);
        atomic_long_dec(&zswap_stored_pages);
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
        zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);
 
        /* zswap entries of length PAGE_SIZE are not compressed. */
-       if (entry->length == PAGE_SIZE) {
+       if (zswap_is_raw(entry->length)) {
                WARN_ON_ONCE(input->length != PAGE_SIZE);
                memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE);
                dlen = PAGE_SIZE;
@@ -1448,7 +1448,7 @@ static bool zswap_store_page(struct page *page,
                obj_cgroup_charge_zswap(objcg, entry->length);
        }
        atomic_long_inc(&zswap_stored_pages);
-       if (entry->length == PAGE_SIZE)
+       if (zswap_is_raw(entry->length))
                atomic_long_inc(&zswap_stored_incompressible_pages);
 
        /*
--
Quoted 2 lines

2.43.0

Those nits aside, LGTM.

I'll leave the naming suggestion to Yosry and Johannes ;)

maintainer
#2 lore raw
Quoted 23 lines · Nhat Pham
Quoted 21 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

Quoted 9 lines
Quoted 8 lines

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Actually I forgot - can you also update the Documentation:

Documentation/admin-guide/cgroup-v2.rst

to include a short description of the new counter? Thanks!

#3 lore raw
Quoted 35 lines · Nhat Pham
Quoted 23 lines · Nhat Pham

On Wed, Feb 4, 2026 at 9:31 PM Jiayuan Chen <[email protected]> wrote:

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

Quoted 9 lines

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Quoted 6 lines

Actually I forgot - can you also update the Documentation:

Documentation/admin-guide/cgroup-v2.rst

to include a short description of the new counter? Thanks!

Thanks Nhat for the review and Acked-by!

I'll update the documentation and add Chris Li's link to changelog in v2. Will consider the selftest as a follow-up.

maintainer
#4 lore raw
In reply to Jiayuan Chen
Quoted 74 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

  $ cgcreate -g memory:test
  $ cgexec -g memory:test ./test_zswpraw &
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 0
  zswpout 0
  zswpwb 0
  $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 104800256
  zswpin 0
  zswpout 51222
  zswpwb 0
  $ pkill test_zswpraw
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 1
  zswpout 51222
  zswpwb 0
Quoted 3 lines

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Overall looks good but as Nhat suggested please update v2 documentation.

Quoted 12 lines
---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h      | 9 +++++++++
 mm/memcontrol.c            | 6 ++++++
 mm/zswap.c                 | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
include/linux/memcontrol.h +0 -0
index b6c82c8f73e1..83d1328f81d1 100644

include/linux/memcontrol.h enum memcg_stat_item { · line 42

Quoted 4 lines
include/linux/memcontrol.h +1 -0
@@ -39,6 +39,7 @@ enum memcg_stat_item {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,

Hmm I don't like the name though. How about INCOMPRESSIBLE or INCOMP for short?

#5 lore raw
Quoted 103 lines · Shakeel Butt
Quoted 75 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0
Quoted 4 lines

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Overall looks good but as Nhat suggested please update v2 documentation.

Quoted 8 lines
---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
Quoted 10 lines

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index b6c82c8f73e1..83d1328f81d1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -39,6 +39,7 @@ enum memcg_stat_item {
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,

Quoted 3 lines

Hmm I don't like the name though. How about INCOMPRESSIBLE or INCOMP for short?

Thanks Shakeel! I'll rename it to INCOMP and update the documentation in v2.

#6 lore raw
In reply to Jiayuan Chen
Quoted 89 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

  $ cgcreate -g memory:test
  $ cgexec -g memory:test ./test_zswpraw &
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 0
  zswpout 0
  zswpwb 0
  $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 104800256
  zswpin 0
  zswpout 51222
  zswpwb 0
  $ pkill test_zswpraw
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 1
  zswpout 51222
  zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h      | 9 +++++++++
 mm/memcontrol.c            | 6 ++++++
 mm/zswap.c                 | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
include/linux/memcontrol.h +0 -0
index b6c82c8f73e1..83d1328f81d1 100644

include/linux/memcontrol.h enum memcg_stat_item { · line 42

Quoted 4 lines
include/linux/memcontrol.h +1 -0
@@ -39,6 +39,7 @@ enum memcg_stat_item {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,

Please change the name as Shakeel suggested.

Quoted 8 lines
include/linux/memcontrol.h +0 -0
 	MEMCG_NR_STAT,
 };
 
include/linux/zswap.h +0 -0
index 30c193a1207e..94f84b154b71 100644

include/linux/zswap.h line 14

Quoted 8 lines
include/linux/zswap.h +5 -0
@@ -7,6 +7,15 @@
 
 struct lruvec;
 
+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)

Internall as well, please rename this to zswap_is_incompressible() or zswap_is_incomp(). Not a big fan of the helper because it doesn't add much, but I don't feel strongly either way.

Quoted 20 lines
include/linux/zswap.h +4 -0
+{
+	return size == PAGE_SIZE;
+}
+
 extern atomic_long_t zswap_stored_pages;
 
 #ifdef CONFIG_ZSWAP
mm/memcontrol.c +1 -0
index 007413a53b45..32fb801530a3 100644
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,
 };
 
 #define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items)

mm/memcontrol.c static const struct memory_stat memory_stats[] = { · line 1350

Quoted 4 lines
mm/memcontrol.c +1 -0
@@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = {
 #ifdef CONFIG_ZSWAP
 	{ "zswap",			MEMCG_ZSWAP_B			},
 	{ "zswapped",			MEMCG_ZSWAPPED			},
+	{ "zswpraw",			MEMCG_ZSWAP_RAW			},

Here as well: zswap_incompressible or zswap_incomp?

Other than the renames and doc, LGTM.

Quoted 55 lines
mm/memcontrol.c +4 -0
 #endif
 	{ "file_mapped",		NR_FILE_MAPPED			},
 	{ "file_dirty",			NR_FILE_DIRTY			},
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);
 	rcu_read_unlock();
 }
 
@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
 	rcu_read_unlock();
 }
 
mm/zswap.c +3 -3
index 3d2d59ac3f9c..54ab4d126f64 100644
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry)
 		obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
 		obj_cgroup_put(entry->objcg);
 	}
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_dec(&zswap_stored_incompressible_pages);
 	zswap_entry_cache_free(entry);
 	atomic_long_dec(&zswap_stored_pages);
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
 	zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);
 
 	/* zswap entries of length PAGE_SIZE are not compressed. */
-	if (entry->length == PAGE_SIZE) {
+	if (zswap_is_raw(entry->length)) {
 		WARN_ON_ONCE(input->length != PAGE_SIZE);
 		memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE);
 		dlen = PAGE_SIZE;
@@ -1448,7 +1448,7 @@ static bool zswap_store_page(struct page *page,
 		obj_cgroup_charge_zswap(objcg, entry->length);
 	}
 	atomic_long_inc(&zswap_stored_pages);
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_inc(&zswap_stored_incompressible_pages);
 
 	/*
Signature · 1 line
2.43.0
#7 lore raw
Quoted 148 lines · Yosry Ahmed
Quoted 85 lines · Jiayuan Chen

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)
Quoted 10 lines

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index b6c82c8f73e1..83d1328f81d1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -39,6 +39,7 @@ enum memcg_stat_item {
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,

Please change the name as Shakeel suggested.

Quoted 10 lines

MEMCG_NR_STAT,
};

diff --git a/include/linux/zswap.h b/include/linux/zswap.h index 30c193a1207e..94f84b154b71 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -7,6 +7,15 @@

Quoted 8 lines

struct lruvec;

+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)

Internall as well, please rename this to zswap_is_incompressible() or zswap_is_incomp(). Not a big fan of the helper because it doesn't add much, but I don't feel strongly either way.

Quoted 19 lines

+{
+ return size == PAGE_SIZE;
+}
+
extern atomic_long_t zswap_stored_pages;

#ifdef CONFIG_ZSWAP
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 007413a53b45..32fb801530a3 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = { MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
};

Quoted 7 lines

#define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items) @@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = { #ifdef CONFIG_ZSWAP
{ "zswap", MEMCG_ZSWAP_B },
{ "zswapped", MEMCG_ZSWAPPED },
+ { "zswpraw", MEMCG_ZSWAP_RAW },

Quoted 5 lines

Here as well: zswap_incompressible or zswap_incomp?

Other than the renames and doc, LGTM.

Quoted 1 line

Thanks Yosry!

I'll rename everything to use "incomp" (MEMCG_ZSWAP_INCOMP, zswap_is_incomp(), "zswap_incomp") and update the documentation in v2.

#8 lore raw
In reply to Jiayuan Chen
Quoted 76 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <[email protected]> wrote:

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

  $ cgcreate -g memory:test
  $ cgexec -g memory:test ./test_zswpraw &
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 0
  zswpout 0
  zswpwb 0
  $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 104800256
  zswpin 0
  zswpout 51222
  zswpwb 0
  $ pkill test_zswpraw
  $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
  zswpraw 0
  zswpin 1
  zswpout 51222
  zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Quoted 7 lines
---
 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h      | 9 +++++++++
 mm/memcontrol.c            | 6 ++++++
 mm/zswap.c                 | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

Quoted 20 lines
include/linux/memcontrol.h +1 -0
index b6c82c8f73e1..83d1328f81d1 100644
@@ -39,6 +39,7 @@ enum memcg_stat_item {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,
 	MEMCG_NR_STAT,
 };
 
include/linux/zswap.h +0 -0
index 30c193a1207e..94f84b154b71 100644
@@ -7,6 +7,15 @@
 
 struct lruvec;

include/linux/zswap.h line 18

Quoted 10 lines
include/linux/zswap.h +9 -0
 
+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+	return size == PAGE_SIZE;
+}
+

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

mm/memcontrol.c static const unsigned int memcg_stat_items[] = { · line 345

Quoted 13 lines
include/linux/zswap.h +0 -0
 extern atomic_long_t zswap_stored_pages;
 
 #ifdef CONFIG_ZSWAP
mm/memcontrol.c +1 -0
index 007413a53b45..32fb801530a3 100644
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = {
 	MEMCG_KMEM,
 	MEMCG_ZSWAP_B,
 	MEMCG_ZSWAPPED,
+	MEMCG_ZSWAP_RAW,
 };

No strong opinion, but I think Shakeel's suggestion of other names is reasonable.

mm/memcontrol.c static const struct memory_stat memory_stats[] = { · line 1350

Quoted 7 lines
mm/memcontrol.c +1 -0
 #define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items)
@@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = {
 #ifdef CONFIG_ZSWAP
 	{ "zswap",			MEMCG_ZSWAP_B			},
 	{ "zswapped",			MEMCG_ZSWAPPED			},
+	{ "zswpraw",			MEMCG_ZSWAP_RAW			},

Ditto.

mm/memcontrol.c void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size) · line 5464

Quoted 9 lines
mm/memcontrol.c +2 -0
 #endif
 	{ "file_mapped",		NR_FILE_MAPPED			},
 	{ "file_dirty",			NR_FILE_DIRTY			},
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);

I understand the helper function is better to receive size_t rather than zswap_entry for this.

Quoted 26 lines
mm/memcontrol.c +2 -0
 	rcu_read_unlock();
 }
 
@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)
 	memcg = obj_cgroup_memcg(objcg);
 	mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
 	mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+	if (zswap_is_raw(size))
+		mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
 	rcu_read_unlock();
 }
 
mm/zswap.c +1 -1
index 3d2d59ac3f9c..54ab4d126f64 100644
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry)
 		obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
 		obj_cgroup_put(entry->objcg);
 	}
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_dec(&zswap_stored_incompressible_pages);
 	zswap_entry_cache_free(entry);
 	atomic_long_dec(&zswap_stored_pages);

mm/zswap.c static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio) · line 947

Quoted 8 lines
mm/zswap.c +1 -1
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
 	zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);
 
 	/* zswap entries of length PAGE_SIZE are not compressed. */
-	if (entry->length == PAGE_SIZE) {
+	if (zswap_is_raw(entry->length)) {
 		WARN_ON_ONCE(input->length != PAGE_SIZE);
 		memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE);
 		dlen = PAGE_SIZE;

Below this part, I show 'dlen == PAGE_SIZE'. Should it also be converted to use the helper function?

Quoted 11 lines
mm/zswap.c +1 -1
@@ -1448,7 +1448,7 @@ static bool zswap_store_page(struct page *page,
 		obj_cgroup_charge_zswap(objcg, entry->length);
 	}
 	atomic_long_inc(&zswap_stored_pages);
-	if (entry->length == PAGE_SIZE)
+	if (zswap_is_raw(entry->length))
 		atomic_long_inc(&zswap_stored_incompressible_pages);
 
 	/*
Signature · 1 line
2.43.0

Looks good to me overall, though. Thank you for this patch.

Thanks,
SJ

#9 lore raw
Quoted 127 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <> wrote:
Quoted 78 lines

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Quoted 8 lines

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

Quoted 21 lines
include/linux/memcontrol.h +0 -0
 index b6c82c8f73e1..83d1328f81d1 100644
 --- a/include/linux/memcontrol.h
 +++ b/include/linux/memcontrol.h
 @@ -39,6 +39,7 @@ enum memcg_stat_item {
 MEMCG_KMEM,
 MEMCG_ZSWAP_B,
 MEMCG_ZSWAPPED,
 + MEMCG_ZSWAP_RAW,
 MEMCG_NR_STAT,
 };
 
 diff --git a/include/linux/zswap.h b/include/linux/zswap.h
 index 30c193a1207e..94f84b154b71 100644
 --- a/include/linux/zswap.h
 +++ b/include/linux/zswap.h
 @@ -7,6 +7,15 @@
 
 struct lruvec;

include/linux/memcontrol.h

Quoted 10 lines
include/linux/memcontrol.h +0 -0
 
 +/*
 + * Check if a zswap entry is stored in raw (uncompressed) form.
 + * This happens when compression doesn't reduce the size.
 + */
 +static inline bool zswap_is_raw(size_t size)
 +{
 + return size == PAGE_SIZE;
 +}
 +

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

Quoted 7 lines

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

I vote to drop the helper.

#10 lore raw
Quoted 136 lines · Yosry Ahmed
Quoted 128 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <> wrote:

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index b6c82c8f73e1..83d1328f81d1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -39,6 +39,7 @@ enum memcg_stat_item {
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
MEMCG_NR_STAT,
};

diff --git a/include/linux/zswap.h b/include/linux/zswap.h index 30c193a1207e..94f84b154b71 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -7,6 +7,15 @@

struct lruvec;

+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+ return size == PAGE_SIZE;
+}
+

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

Quoted 8 lines

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

Quoted 2 lines

I vote to drop the helper.

The reason I introduced the helper is that the incompressible check now lives in two places:

In zswap.c - for the global zswap_stored_incompressible_pages counter In memcontrol.c - for the per-memcg MEMCG_ZSWAP_INCOMP stat

By extracting a shared helper, both modules use the same logic, which helps with maintainability.

That said, I'm fine with dropping the helper if preferred. I can add a comment in memcontrol.c explaining the logic. My only concern is that if the incompressible detection logic in zswap ever changes, someone might forget to update the memcg accounting accordingly.

But perhaps that's an unlikely scenario.

#11 lore raw
Quoted 142 lines
Quoted 135 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <> wrote:

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index b6c82c8f73e1..83d1328f81d1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -39,6 +39,7 @@ enum memcg_stat_item {
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
MEMCG_NR_STAT,
};

diff --git a/include/linux/zswap.h b/include/linux/zswap.h index 30c193a1207e..94f84b154b71 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -7,6 +7,15 @@

struct lruvec;

+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+ return size == PAGE_SIZE;
+}
+

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

Quoted 2 lines

I vote to drop the helper.

The reason I introduced the helper is that the incompressible check now lives in two places:

In zswap.c - for the global zswap_stored_incompressible_pages counter In memcontrol.c - for the per-memcg MEMCG_ZSWAP_INCOMP stat

Quoted 7 lines

By extracting a shared helper, both modules use the same logic, which helps with maintainability.

That said, I'm fine with dropping the helper if preferred. I can add a comment in memcontrol.c explaining the logic. My only concern is that if the incompressible detection logic in zswap ever changes, someone might forget to update the memcg accounting accordingly.

But perhaps that's an unlikely scenario.

Well, a selftest would be the right way to detect such a problem imo. Even if we need to have a customer definition for incompressible later, it should remain in zswap and we should pass it into memcg code.

For now, I think let's keep open-coding the PAGE_SIZE check.

#12 lore raw
In reply to SeongJae Park
Quoted 213 lines · SeongJae Park
Quoted 77 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <[email protected]> wrote:

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

Quoted 8 lines

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

Quoted 21 lines
include/linux/memcontrol.h +0 -0
 index b6c82c8f73e1..83d1328f81d1 100644
 --- a/include/linux/memcontrol.h
 +++ b/include/linux/memcontrol.h
 @@ -39,6 +39,7 @@ enum memcg_stat_item {
 MEMCG_KMEM,
 MEMCG_ZSWAP_B,
 MEMCG_ZSWAPPED,
 + MEMCG_ZSWAP_RAW,
 MEMCG_NR_STAT,
 };
 
 diff --git a/include/linux/zswap.h b/include/linux/zswap.h
 index 30c193a1207e..94f84b154b71 100644
 --- a/include/linux/zswap.h
 +++ b/include/linux/zswap.h
 @@ -7,6 +7,15 @@
 
 struct lruvec;

include/linux/memcontrol.h

Quoted 10 lines
include/linux/memcontrol.h +0 -0
 
 +/*
 + * Check if a zswap entry is stored in raw (uncompressed) form.
 + * This happens when compression doesn't reduce the size.
 + */
 +static inline bool zswap_is_raw(size_t size)
 +{
 + return size == PAGE_SIZE;
 +}
 +

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

Quoted 15 lines

extern atomic_long_t zswap_stored_pages;

#ifdef CONFIG_ZSWAP
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 007413a53b45..32fb801530a3 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = { MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
};

No strong opinion, but I think Shakeel's suggestion of other names is reasonable.

Quoted 8 lines

#define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items)
@@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = { #ifdef CONFIG_ZSWAP
{ "zswap", MEMCG_ZSWAP_B },
{ "zswapped", MEMCG_ZSWAPPED },
+ { "zswpraw", MEMCG_ZSWAP_RAW },

Ditto.

Quoted 11 lines

#endif
{ "file_mapped", NR_FILE_MAPPED },
{ "file_dirty", NR_FILE_DIRTY },
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size) memcg = obj_cgroup_memcg(objcg);
mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+ if (zswap_is_raw(size))
+ mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);

I understand the helper function is better to receive size_t rather than zswap_entry for this.

Quoted 29 lines

rcu_read_unlock();
}

@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size) memcg = obj_cgroup_memcg(objcg);
mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+ if (zswap_is_raw(size))
+ mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
rcu_read_unlock();
}

diff --git a/mm/zswap.c b/mm/zswap.c
index 3d2d59ac3f9c..54ab4d126f64 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry) obj_cgroup_uncharge_zswap(entry->objcg, entry->length); obj_cgroup_put(entry->objcg);
}
- if (entry->length == PAGE_SIZE)
+ if (zswap_is_raw(entry->length)) atomic_long_dec(&zswap_stored_incompressible_pages); zswap_entry_cache_free(entry);
atomic_long_dec(&zswap_stored_pages);
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio) zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);

Quoted 7 lines

/* zswap entries of length PAGE_SIZE are not compressed. */
- if (entry->length == PAGE_SIZE) {
+ if (zswap_is_raw(entry->length)) { WARN_ON_ONCE(input->length != PAGE_SIZE); memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE); dlen = PAGE_SIZE;

Quoted 3 lines

Below this part, I show 'dlen == PAGE_SIZE'. Should it also be converted to use the helper function?

The dlen variable represents the decompressed (plaintext) size. Since we compress individual pages, the decompressed output should always be PAGE_SIZE in normal cases.

This check validates whether decompression produced the expected result, not whether the entry is incompressible.

Using zswap_is_incomp() here would be semantically incorrect - the helper is meant to check if an entry was stored without compression (i.e., compression failed to reduce size), while dlen == PAGE_SIZE verifies the output of decompression is valid.

#13 lore raw
Quoted 5 lines · On Fri, 06 Feb 2026 02:47:58 +0000 "Jiayuan Chen" <[email protected]>…
Quoted 2 lines · SeongJae Park

On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <[email protected]> wrote:

[...]

Quoted 18 lines
Quoted 10 lines
Quoted 10 lines

@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio) zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);

/* zswap entries of length PAGE_SIZE are not compressed. */
- if (entry->length = PAGE_SIZE) {
+ if (zswap_is_raw(entry->length)) { WARN_ON_ONCE(input->length != PAGE_SIZE); memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE); dlen = PAGE_SIZE;

Quoted 3 lines

Below this part, I show 'dlen = PAGE_SIZE'. Should it also be converted to use the helper function?

The dlen variable represents the decompressed (plaintext) size. Since we compress individual pages, the decompressed output should always be PAGE_SIZE in normal cases.

Quoted 5 lines

This check validates whether decompression produced the expected result, not whether the entry is incompressible.

Using zswap_is_incomp() here would be semantically incorrect - the helper is meant to check if an entry was stored without compression (i.e., compression failed to reduce size), while dlen = PAGE_SIZE verifies the output of decompression is valid.

You are right. Thank you for kindly correcting me.

Thanks,
SJ

#14 lore raw
In reply to Jiayuan Chen
Quoted 218 lines · On Thu, 5 Feb 2026 13:30:12 +0800 Jiayuan Chen <[email protected]> wrote:
Quoted 211 lines

From: Jiayuan Chen <[email protected]>

The global zswap_stored_incompressible_pages counter was added in commit dca4437a5861 ("mm/zswap: store <PAGE_SIZE compression failed page as-is") to track how many pages are stored in raw (uncompressed) form in zswap. However, in containerized environments, knowing which cgroup is contributing incompressible pages is essential for effective resource management.

Add a new memcg stat 'zswpraw' to track incompressible pages per cgroup. This helps administrators and orchestrators to:

1. Identify workloads that produce incompressible data (e.g., encrypted data, already-compressed media, random data) and may not benefit from zswap.

2. Make informed decisions about workload placement - moving incompressible workloads to nodes with larger swap backing devices rather than relying on zswap.

3. Debug zswap efficiency issues at the cgroup level without needing to correlate global stats with individual cgroups.

While the compression ratio can be estimated from existing stats (zswap / zswapped * PAGE_SIZE), this doesn't distinguish between "uniformly poor compression" and "a few completely incompressible pages mixed with highly compressible ones". The zswpraw stat provides direct visibility into the latter case.

Changes
-------

1. Add zswap_is_raw() helper (include/linux/zswap.h)
- Abstract the PAGE_SIZE comparison logic for identifying raw entries
- Keep the incompressible check in one place for maintainability

2. Add MEMCG_ZSWAP_RAW stat definition (include/linux/memcontrol.h, mm/memcontrol.c)
- Add MEMCG_ZSWAP_RAW to memcg_stat_item enum
- Register in memcg_stat_items[] and memory_stats[] arrays
- Export as "zswpraw" in memory.stat

3. Update statistics accounting (mm/memcontrol.c, mm/zswap.c)
- Track MEMCG_ZSWAP_RAW in obj_cgroup_charge/uncharge_zswap()
- Use zswap_is_raw() helper in zswap.c for consistency

Test
----

I wrote a simple test program[1] that allocates memory and compresses it with zstd, so kernel zswap cannot compress further.

 $ cgcreate -g memory:test
 $ cgexec -g memory:test ./test_zswpraw &
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 0
 zswpout 0
 zswpwb 0
 $ echo "100M" > /sys/fs/cgroup/test/memory.reclaim
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 104800256
 zswpin 0
 zswpout 51222
 zswpwb 0
 $ pkill test_zswpraw
 $ cat /sys/fs/cgroup/test/memory.stat | grep zswp
 zswpraw 0
 zswpin 1
 zswpout 51222
 zswpwb 0

[1] https://gist.github.com/mrpre/00432c6154250326994fbeaf62e0e6f1

---

 include/linux/memcontrol.h | 1 +
 include/linux/zswap.h | 9 +++++++++
 mm/memcontrol.c | 6 ++++++
 mm/zswap.c | 6 +++---
 4 files changed, 19 insertions(+), 3 deletions(-)

As others also mentioned, the documentation of the new stat would be needed.

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index b6c82c8f73e1..83d1328f81d1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -39,6 +39,7 @@ enum memcg_stat_item {
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
MEMCG_NR_STAT,
};

diff --git a/include/linux/zswap.h b/include/linux/zswap.h index 30c193a1207e..94f84b154b71 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -7,6 +7,15 @@

struct lruvec;

+/*
+ * Check if a zswap entry is stored in raw (uncompressed) form.
+ * This happens when compression doesn't reduce the size.
+ */
+static inline bool zswap_is_raw(size_t size)
+{
+ return size == PAGE_SIZE;
+}
+

No strong opinion, but I'm not really sure if the helper is needed, because it feels quite simple logic:

"If an object is compressed and the size is same to the original one, the object is incompressible."

I also feel the function name bit odd, given the type of the parameter. Based on the function name and the comment, I'd expect it to receive a zswap_entry object. I understand it is better to receive a size_t, to be called from obj_cgroup_[un]charge_zswap(), though. Even in the case, I think the name can be better (e.g., zswap_compression_failed() or zswap_was_incompressible() ?), or at least the coment can be more kindly explain the fact that the parameter is the size of object after the compression attempt.

extern atomic_long_t zswap_stored_pages;

#ifdef CONFIG_ZSWAP
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 007413a53b45..32fb801530a3 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -341,6 +341,7 @@ static const unsigned int memcg_stat_items[] = { MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
+ MEMCG_ZSWAP_RAW,
};

No strong opinion, but I think Shakeel's suggestion of other names is reasonable.

#define NR_MEMCG_NODE_STAT_ITEMS ARRAY_SIZE(memcg_node_stat_items) @@ -1346,6 +1347,7 @@ static const struct memory_stat memory_stats[] = { #ifdef CONFIG_ZSWAP
{ "zswap", MEMCG_ZSWAP_B },
{ "zswapped", MEMCG_ZSWAPPED },
+ { "zswpraw", MEMCG_ZSWAP_RAW },

Ditto.

#endif
{ "file_mapped", NR_FILE_MAPPED },
{ "file_dirty", NR_FILE_DIRTY },
@@ -5458,6 +5460,8 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size) memcg = obj_cgroup_memcg(objcg);
mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
+ if (zswap_is_raw(size))
+ mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, 1);

I understand the helper function is better to receive size_t rather than zswap_entry for this.

rcu_read_unlock();
}

@@ -5481,6 +5485,8 @@ void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size) memcg = obj_cgroup_memcg(objcg);
mod_memcg_state(memcg, MEMCG_ZSWAP_B, -size);
mod_memcg_state(memcg, MEMCG_ZSWAPPED, -1);
+ if (zswap_is_raw(size))
+ mod_memcg_state(memcg, MEMCG_ZSWAP_RAW, -1);
rcu_read_unlock();
}

diff --git a/mm/zswap.c b/mm/zswap.c
index 3d2d59ac3f9c..54ab4d126f64 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -723,7 +723,7 @@ static void zswap_entry_free(struct zswap_entry *entry) obj_cgroup_uncharge_zswap(entry->objcg, entry->length); obj_cgroup_put(entry->objcg);
}
- if (entry->length == PAGE_SIZE)
+ if (zswap_is_raw(entry->length)) atomic_long_dec(&zswap_stored_incompressible_pages); zswap_entry_cache_free(entry);
atomic_long_dec(&zswap_stored_pages);
@@ -941,7 +941,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio) zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);

/* zswap entries of length PAGE_SIZE are not compressed. */
- if (entry->length == PAGE_SIZE) {
+ if (zswap_is_raw(entry->length)) { WARN_ON_ONCE(input->length != PAGE_SIZE); memcpy_from_sglist(kmap_local_folio(folio, 0), input, 0, PAGE_SIZE); dlen = PAGE_SIZE;

Quoted 3 lines

Below this part, I show 'dlen == PAGE_SIZE'. Should it also be converted to use the helper function?

The dlen variable represents the decompressed (plaintext) size. Since we compress individual pages, the decompressed output should always be PAGE_SIZE in normal cases.

Quoted 5 lines

This check validates whether decompression produced the expected result, not whether the entry is incompressible.

Using zswap_is_incomp() here would be semantically incorrect - the helper is meant to check if an entry was stored without compression (i.e., compression failed to reduce size), while dlen == PAGE_SIZE verifies the output of decompression is valid.

This confusion is exactly why we should drop the helper.