diff mbox series

[v3,3/3] vsprintf: introduce new format to dump full information of page flags

Message ID 20210208101439.55474-4-laoar.shao@gmail.com (mailing list archive)
State New, archived
Headers show
Series mm, vsprintf: introduce new format to dump full information of page flags | expand

Commit Message

Yafang Shao Feb. 8, 2021, 10:14 a.m. UTC
The existed pGp shows the names of page flags only, rather than the full
information including section, node, zone, last cpuipid and kasan tag.
While it is not easy to parse these information manually because there
are so many flavors. We'd better interpret them in printf.

To avoid breaking some tools which parsing pGp via debugfs or affecting
the printf buffer, other new formats are introduced, so the user can choose
what and in which order they want, suggested by Andy. These new introduced
format as follows,
    pGpb: print other information first and then the names of page flags
    pGpl: print the names of page flags first and then the other info

The differece between them looks like the difference between big-endian and
little-endian, that's why they are named like that. The examples of the
output as follows,
    %pGpb 0x17ffffc0010200(node=0|zone=2|lastcpupid=0x1fffff|slab|head)
    %pGpl 0x17ffffc0010200(slab|head|node=0|zone=2|lastcpupid=0x1fffff)

To be compitable with the existed format of pGp, the new introduced ones
also use '|' as the separator, then the user tools parsing pGp won't
need to make change, suggested by Matthew.

The doc and test cases are also updated. Below is the output of the
test cases,
[ 4299.847655] test_printf: loaded.
[ 4299.848301] test_printf: all 404 tests passed
[ 4299.850371] test_printf: unloaded.

Cc: David Hildenbrand <david@redhat.com>
Cc: Joe Perches <joe@perches.com>
Cc: Miaohe Lin <linmiaohe@huawei.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
 Documentation/core-api/printk-formats.rst |   2 +
 lib/test_printf.c                         | 126 +++++++++++++++++++---
 lib/vsprintf.c                            | 115 +++++++++++++++++++-
 3 files changed, 226 insertions(+), 17 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 6d26c5c6ac48..56f8e0fc3963 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -539,6 +539,8 @@  Flags bitfields such as page flags, gfp_flags
 ::
 
 	%pGp	referenced|uptodate|lru|active|private
+	%pGpb	node=0|zone=2|referenced|uptodate|lru|active|private
+	%pGpl	referenced|uptodate|lru|active|private|node=0|zone=2
 	%pGg	GFP_USER|GFP_DMA32|GFP_NOWARN
 	%pGv	read|exec|mayread|maywrite|mayexec|denywrite
 
diff --git a/lib/test_printf.c b/lib/test_printf.c
index 7ac87f18a10f..af2945bb730a 100644
--- a/lib/test_printf.c
+++ b/lib/test_printf.c
@@ -569,24 +569,130 @@  netdev_features(void)
 {
 }
 
+static void  __init
+page_flags_build_info(char *cmp_buf, int prefix, int sec, int node, int zone,
+		      int last_cpupid, int kasan_tag, unsigned long *flags)
+{
+	unsigned long size = prefix;
+
+#ifdef SECTION_IN_PAGE_FLAGS
+	*flags |= (sec & SECTIONS_MASK) << SECTIONS_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "section=%#x|", sec);
+	size = strlen(cmp_buf);
+#endif
+
+	*flags |= ((node & NODES_MASK) << NODES_PGSHIFT) |
+		  ((zone & ZONES_MASK) << ZONES_PGSHIFT);
+	snprintf(cmp_buf + size, BUF_SIZE - size, "node=%d|zone=%d", node, zone);
+	size = strlen(cmp_buf);
+
+#ifndef LAST_CPUPID_NOT_IN_PAGE_FLAGS
+	*flags |= (last_cpupid & LAST_CPUPID_MASK) << LAST_CPUPID_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "|lastcpupid=%#x", last_cpupid);
+	size = strlen(cmp_buf);
+#endif
+
+#if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
+	*flags |= (tag & KASAN_TAG_MASK) << KASAN_TAG_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "|kasantag=%#x", tag);
+	size = strlen(cmp_buf);
+#endif
+}
+
 static void __init
-flags(void)
+page_flags_build_names(char *cmp_buf, int prefix, const char *expect,
+		       unsigned long flags, unsigned long *page_flags)
 {
+	*page_flags |= flags;
+	snprintf(cmp_buf + prefix, BUF_SIZE - prefix, "%s", expect);
+}
+
+static void __init
+__page_flags_test(const char *expect, unsigned long flags)
+{
+	test(expect, "%pGp", &flags);
+}
+
+static void __init
+page_flags_test_be(char *cmp_buf, int sec, int node, int zone,
+		   int last_cpupid, int kasan_tag, const char *name,
+		   unsigned long flags)
+{
+	unsigned long page_flags = 0;
+	int size;
+
+	page_flags_build_info(cmp_buf, 0, sec, node, zone, last_cpupid,
+			      kasan_tag, &page_flags);
+
+	if (*name) {
+		size = strlen(cmp_buf);
+		if (size < BUF_SIZE - 2) {
+			*(cmp_buf + size) = '|';
+			*(cmp_buf + size + 1) = '\0';
+		}
+		page_flags_build_names(cmp_buf, strlen(cmp_buf), name, flags, &page_flags);
+	}
+
+	test(cmp_buf, "%pGpb", &page_flags);
+}
+
+static void __init
+page_flags_test_le(char *cmp_buf, int sec, int node, int zone,
+		   int last_cpupid, int kasan_tag, const char *name,
+		   unsigned long flags)
+{
+	unsigned long page_flags = 0;
+	int size = 0;
+
+	if (*name) {
+		page_flags_build_names(cmp_buf, 0, name, flags, &page_flags);
+		size = strlen(cmp_buf);
+		if (size < BUF_SIZE - 2) {
+			*(cmp_buf + size) = '|';
+			*(cmp_buf + size + 1) = '\0';
+		}
+		size = strlen(cmp_buf);
+	}
+
+	page_flags_build_info(cmp_buf, size, sec, node, zone, last_cpupid,
+			      kasan_tag, &page_flags);
+
+	test(cmp_buf, "%pGpl", &page_flags);
+}
+
+static void __init
+page_flags_test(char *cmp_buf)
+{
+	char *name = "uptodate|dirty|lru|active|swapbacked";
 	unsigned long flags;
-	gfp_t gfp;
-	char *cmp_buffer;
 
 	flags = 0;
-	test("", "%pGp", &flags);
+	__page_flags_test("", flags);
+	page_flags_test_be(cmp_buf, 0, 0, 0, 0, 0, "", flags);
+	page_flags_test_le(cmp_buf, 0, 0, 0, 0, 0, "", flags);
 
-	/* Page flags should filter the zone id */
 	flags = 1UL << NR_PAGEFLAGS;
-	test("", "%pGp", &flags);
+	__page_flags_test("", flags);
 
 	flags |= 1UL << PG_uptodate | 1UL << PG_dirty | 1UL << PG_lru
-		| 1UL << PG_active | 1UL << PG_swapbacked;
-	test("uptodate|dirty|lru|active|swapbacked", "%pGp", &flags);
+		 | 1UL << PG_active | 1UL << PG_swapbacked;
+	__page_flags_test(name, flags);
+	page_flags_test_be(cmp_buf, 1, 1, 1, 0x1fffff, 1, name, flags);
+	page_flags_test_le(cmp_buf, 1, 1, 1, 0x1fffff, 1, name, flags);
+}
+
+static void __init
+flags(void)
+{
+	unsigned long flags;
+	gfp_t gfp;
+	char *cmp_buffer;
 
+	cmp_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
+	if (!cmp_buffer)
+		return;
+
+	page_flags_test(cmp_buffer);
 
 	flags = VM_READ | VM_EXEC | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC
 			| VM_DENYWRITE;
@@ -601,10 +707,6 @@  flags(void)
 	gfp = __GFP_ATOMIC;
 	test("__GFP_ATOMIC", "%pGg", &gfp);
 
-	cmp_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
-	if (!cmp_buffer)
-		return;
-
 	/* Any flags not translated by the table should remain numeric */
 	gfp = ~__GFP_BITS_MASK;
 	snprintf(cmp_buffer, BUF_SIZE, "%#lx", (unsigned long) gfp);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 14c9a6af1b23..c912cc9bddb0 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1916,6 +1916,115 @@  char *format_flags(char *buf, char *end, unsigned long flags,
 	return buf;
 }
 
+struct page_flags_layout {
+	int width;
+	int shift;
+	int mask;
+	const struct printf_spec *spec;
+	const char *name;
+};
+
+static const struct page_flags_layout pfl[] = {
+	{SECTIONS_WIDTH, SECTIONS_PGSHIFT, SECTIONS_MASK,
+	 &default_dec_spec, "section"},
+	{NODES_WIDTH, NODES_PGSHIFT, NODES_MASK,
+	 &default_dec_spec, "node"},
+	{ZONES_WIDTH, ZONES_PGSHIFT, ZONES_MASK,
+	 &default_dec_spec, "zone"},
+	{LAST_CPUPID_WIDTH, LAST_CPUPID_PGSHIFT, LAST_CPUPID_MASK,
+	 &default_flag_spec, "lastcpupid"},
+	{KASAN_TAG_WIDTH, KASAN_TAG_PGSHIFT, KASAN_TAG_MASK,
+	 &default_flag_spec, "kasantag"},
+};
+
+static
+char *__format_page_flags(char *buf, char *end, unsigned long flags)
+{
+	DECLARE_BITMAP(mask, ARRAY_SIZE(pfl));
+	unsigned long last;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pfl); i++)
+		__assign_bit(i, mask, pfl[i].width);
+
+	last = find_last_bit(mask, ARRAY_SIZE(pfl));
+
+	for_each_set_bit(i, mask, ARRAY_SIZE(pfl)) {
+		/* Format: Flag Name + '=' (equals sign) + Number + '|' (separator) */
+		buf = string(buf, end, pfl[i].name, *pfl[i].spec);
+
+		if (buf < end)
+			*buf = '=';
+		buf++;
+		buf = number(buf, end, (flags >> pfl[i].shift) & pfl[i].mask,
+			     *pfl[i].spec);
+
+		/* No separator for the last entry */
+		if (i != last) {
+			if (buf < end)
+				*buf = '|';
+			buf++;
+		}
+	}
+
+	return buf;
+}
+
+static
+char *format_page_flags_be(char *buf, char *end, unsigned long flags)
+{
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	buf = __format_page_flags(buf, end, flags);
+
+	if (flags & mask) {
+		if (buf < end)
+			*buf = '|';
+		buf++;
+	}
+
+	return format_flags(buf, end, flags & mask, pageflag_names);
+}
+
+static
+char *format_page_flags_le(char *buf, char *end, unsigned long flags)
+{
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	buf = format_flags(buf, end, flags & mask, pageflag_names);
+
+	if (flags & mask) {
+		if (buf < end)
+			*buf = '|';
+		buf++;
+	}
+
+	return __format_page_flags(buf, end, flags & ~mask);
+}
+
+static
+char *format_page_flags(char *buf, char *end, void *flags_ptr,
+			struct printf_spec spec, const char *fmt)
+{
+	unsigned long flags = *(unsigned long *)flags_ptr;
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	if (strlen(fmt) == 2) {
+		flags &= mask;
+		return format_flags(buf, end, flags, pageflag_names);
+	}
+
+	switch (fmt[2]) {
+	case 'b':
+		return format_page_flags_be(buf, end, flags);
+	case 'l':
+		return format_page_flags_le(buf, end, flags);
+	default:
+		flags &= mask;
+		return format_flags(buf, end, flags, pageflag_names);
+	}
+}
+
 static noinline_for_stack
 char *flags_string(char *buf, char *end, void *flags_ptr,
 		   struct printf_spec spec, const char *fmt)
@@ -1928,11 +2037,7 @@  char *flags_string(char *buf, char *end, void *flags_ptr,
 
 	switch (fmt[1]) {
 	case 'p':
-		flags = *(unsigned long *)flags_ptr;
-		/* Remove zone id */
-		flags &= (1UL << NR_PAGEFLAGS) - 1;
-		names = pageflag_names;
-		break;
+		return format_page_flags(buf, end, flags_ptr, spec, fmt);
 	case 'v':
 		flags = *(unsigned long *)flags_ptr;
 		names = vmaflag_names;