[RFC PATCH 3/4] mm/damon/perf: add KUnit tests for the SPE record parser

Kunwu Chan <[email protected]>
Newsgroups dev.linux.lists.damon,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.linux-perf-users,org.kvack.linux-mm
Message-ID <[email protected]>
From: "Lian Wang (ProcessMission)" <[email protected]>

Add byte-exact tests for load and store records, timestamp terminators,
multiple records, PAD and ALIGNMENT packets, extended addresses, invalid
extended headers, error resynchronization, and records without a virtual
address.

Cover ALIGNMENT packets at both odd and already aligned stream offsets.
Also verify that a record split across two AUX snapshots leaves the tail
unchanged until the terminating packet becomes available.

Co-developed-by: Kunwu Chan <[email protected]>
Signed-off-by: Kunwu Chan <[email protected]>
Signed-off-by: Lian Wang (ProcessMission) <[email protected]>
---
 mm/damon/perf/spe_parser_test.c | 365 ++++++++++++++++++++++++++++++++
 1 file changed, 365 insertions(+)
 create mode 100644 mm/damon/perf/spe_parser_test.c

diff --git a/mm/damon/perf/spe_parser_test.c b/mm/damon/perf/spe_parser_test.c
new file mode 100644
index 000000000000..598d9fd7cdc7
--- /dev/null
+++ b/mm/damon/perf/spe_parser_test.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the DAMON perf ARM SPE record parser.
+ *
+ * Each parameterized case feeds a byte-exact SPE packet stream (same
+ * encodings and decode order as tools/perf/util/arm-spe-decoder) into
+ * spe_parse_one_record() and verifies the synthesized records, the
+ * return values, and the aux_tail accounting.  The loop mirrors
+ * spe_backend_drain() including the caller-side window shrink.
+ */
+
+#include <kunit/test.h>
+#include <linux/slab.h>
+
+#include "spe_parser.h"
+
+/**
+ * struct spe_parse_case - One parser test case.
+ * @name:	Parameter description (shown on failure).
+ * @stream:	Byte-exact SPE packet stream.
+ * @len:	@stream length.
+ * @exp_reports:	Expected SPE_PARSE_REPORT count.
+ * @exp_skips:		Expected SPE_PARSE_SKIP count.
+ * @exp_errors:		Expected SPE_PARSE_ERROR count.
+ * @exp_tail:		Expected st->aux_tail after the stream.
+ * @exp_va: Expected virtual address of the first REPORT record.
+ * @exp_tid: Expected tid of the first REPORT record.
+ * @exp_is_write: Expected access type of the first REPORT record.
+ */
+struct spe_parse_case {
+	const char *name;
+	const u8 *stream;
+	size_t len;
+	unsigned int exp_reports;
+	unsigned int exp_skips;
+	unsigned int exp_errors;
+	unsigned long exp_tail;
+	unsigned long exp_va;
+	u32 exp_tid;
+	bool exp_is_write;
+};
+
+static const u8 stream_store[] = {
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x01,					/* OP-TYPE: ST */
+	0x01,						/* END */
+};
+
+static const u8 stream_ts_load[] = {
+	0xb2, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x2000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* TIMESTAMP end */
+};
+
+static const u8 stream_two[] = {
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x01,					/* OP-TYPE: ST */
+	0x01,						/* END */
+	0xb2, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x2000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END */
+};
+
+static const u8 stream_pad[] = {
+	0x00, 0x00,					/* PAD prefix */
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x01,					/* OP-TYPE: ST */
+	0x01,						/* END */
+	0x00, 0x00, 0x00,				/* PAD padding */
+};
+
+static const u8 stream_alignment[] = {
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: tid 42, pos 0-4 */
+	0x20, 0x00,					/* ALIGNMENT at odd pos 5 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END */
+};
+
+static const u8 stream_alignment_aligned[] = {
+	0x20, 0x00,					/* ALIGNMENT at even pos 0 */
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END */
+};
+
+static const u8 stream_bad[] = {
+	0xff,						/* unknown header */
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x01,					/* OP-TYPE: ST */
+	0x01,						/* END */
+};
+
+static const u8 stream_skip[] = {
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END, no address */
+};
+
+static const u8 stream_other_pkts[] = {
+	0x42, 0x05,					/* EVENTS (width 1) */
+	0x43, 0x06,					/* DATA-SOURCE (width 1) */
+	0x98, 0x00, 0x00,				/* COUNTER (width 2) */
+	0xb2, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x3000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END */
+};
+
+static const u8 stream_truncated[] = {
+	0x66, 0x2a, 0x00, 0x00, 0x00,			/* CONTEXT: 64-bit EL1 tid=42 */
+	0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x1000 */
+	0x49, 0x01,					/* OP-TYPE: ST, no END */
+};
+
+static const u8 stream_truncated_packet[] = {
+	0xb2, 0x00, 0x10,				/* short 8-byte address */
+};
+
+static const u8 stream_ext_addr[] = {
+	0x20, 0xb2,					/* EXTENDED ADDRESS, DATA_VIRT */
+	0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* VA 0x4000 */
+	0x49, 0x00,					/* OP-TYPE: load */
+	0x01,						/* END */
+};
+
+static const u8 stream_invalid_extended[] = {
+	0x20, 0x42, 0x00,				/* invalid extended EVENTS */
+	0x01,						/* END after resync */
+};
+
+static const u8 stream_pad_only[] = {
+	0x00, 0x00, 0x00,
+};
+
+static const u8 stream_empty[] = { 0x00 };
+
+static const struct spe_parse_case spe_parse_cases[] = {
+	{
+		.name = "store record",
+		.stream = stream_store,
+		.len = sizeof(stream_store),
+		.exp_reports = 1,
+		.exp_tail = 17,
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = true,
+	},
+	{
+		.name = "load record with timestamp terminator",
+		.stream = stream_ts_load,
+		.len = sizeof(stream_ts_load),
+		.exp_reports = 1,
+		.exp_tail = 20,
+		.exp_va = 0x2000,
+		.exp_tid = 0,
+		.exp_is_write = false,
+	},
+	{
+		.name = "two records in one window",
+		.stream = stream_two,
+		.len = sizeof(stream_two),
+		.exp_reports = 2,
+		.exp_tail = 29,
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = true,
+	},
+	{
+		.name = "pad-wrapped record",
+		.stream = stream_pad,
+		.len = sizeof(stream_pad),
+		.exp_reports = 1,
+		.exp_tail = 22,
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = true,
+	},
+	{
+		.name = "alignment packet at odd position",
+		.stream = stream_alignment,
+		.len = sizeof(stream_alignment),
+		.exp_reports = 1,
+		.exp_tail = 19,
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = false,
+	},
+	{
+		.name = "alignment packet at aligned position",
+		.stream = stream_alignment_aligned,
+		.len = sizeof(stream_alignment_aligned),
+		.exp_reports = 1,
+		.exp_tail = sizeof(stream_alignment_aligned),
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = false,
+	},
+	{
+		.name = "bad packet resync",
+		.stream = stream_bad,
+		.len = sizeof(stream_bad),
+		.exp_reports = 1,
+		.exp_errors = 1,
+		.exp_tail = 18,
+		.exp_va = 0x1000,
+		.exp_tid = 42,
+		.exp_is_write = true,
+	},
+	{
+		.name = "record without address",
+		.stream = stream_skip,
+		.len = sizeof(stream_skip),
+		.exp_skips = 1,
+		.exp_tail = 3,
+	},
+	{
+		.name = "events/source/counter packets ignored",
+		.stream = stream_other_pkts,
+		.len = sizeof(stream_other_pkts),
+		.exp_reports = 1,
+		.exp_tail = 19,
+		.exp_va = 0x3000,
+		.exp_is_write = false,
+	},
+	{
+		.name = "truncated trailing record retained",
+		.stream = stream_truncated,
+		.len = sizeof(stream_truncated),
+		.exp_tail = 0,
+	},
+	{
+		.name = "truncated packet retained",
+		.stream = stream_truncated_packet,
+		.len = sizeof(stream_truncated_packet),
+		.exp_tail = 0,
+	},
+	{
+		.name = "extended address packet",
+		.stream = stream_ext_addr,
+		.len = sizeof(stream_ext_addr),
+		.exp_reports = 1,
+		.exp_tail = 13,
+		.exp_va = 0x4000,
+		.exp_is_write = false,
+	},
+	{
+		.name = "invalid extended header resync",
+		.stream = stream_invalid_extended,
+		.len = sizeof(stream_invalid_extended),
+		.exp_skips = 1,
+		.exp_errors = 1,
+		.exp_tail = sizeof(stream_invalid_extended),
+	},
+	{
+		.name = "pad-only window",
+		.stream = stream_pad_only,
+		.len = sizeof(stream_pad_only),
+		.exp_tail = 3,
+	},
+	{
+		.name = "empty window",
+		.stream = stream_empty,
+		.len = 0,
+		.exp_tail = 0,
+	},
+};
+
+KUNIT_ARRAY_PARAM_DESC(spe_parse, spe_parse_cases, name);
+
+static void spe_parse_case_test(struct kunit *test)
+{
+	const struct spe_parse_case *tc = test->param_value;
+	struct spe_parser_state st = { 0 };
+	struct spe_record rec;
+	u8 *buf;
+	unsigned int reports = 0, skips = 0, errors = 0, guard = 0;
+	bool first_checked = false;
+
+	buf = kunit_kmalloc(test, tc->len ?: 1, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+	memcpy(buf, tc->stream, tc->len);
+	st.win = buf;
+	st.win_size = tc->len;
+
+	while (guard++ < SPE_BUFFER_MAX_RECORDS) {
+		unsigned long tail0 = st.aux_tail;
+		unsigned long consumed;
+		int ret = spe_parse_one_record(&st, &rec);
+
+		if (ret == SPE_PARSE_NEED_MORE)
+			break;
+
+		/* caller-side window shrink, mirrors spe_backend_drain() */
+		consumed = st.aux_tail - tail0;
+		st.win_size -= consumed;
+		memmove(st.win, st.win + consumed, st.win_size);
+
+		switch (ret) {
+		case SPE_PARSE_REPORT:
+			reports++;
+			if (!first_checked) {
+				KUNIT_EXPECT_EQ(test, tc->exp_va, rec.va);
+				KUNIT_EXPECT_EQ(test, tc->exp_tid, rec.tid);
+				KUNIT_EXPECT_EQ(test, tc->exp_is_write,
+						rec.is_write);
+				first_checked = true;
+			}
+			break;
+		case SPE_PARSE_SKIP:
+			skips++;
+			break;
+		case SPE_PARSE_ERROR:
+			errors++;
+			break;
+		}
+	}
+
+	KUNIT_EXPECT_EQ(test, tc->exp_reports, reports);
+	KUNIT_EXPECT_EQ(test, tc->exp_skips, skips);
+	KUNIT_EXPECT_EQ(test, tc->exp_errors, errors);
+	KUNIT_EXPECT_EQ(test, tc->exp_tail, st.aux_tail);
+	KUNIT_EXPECT_EQ(test, tc->exp_tail, st.bytes);
+	KUNIT_EXPECT_EQ(test, tc->exp_reports, st.records);
+}
+
+static void spe_split_record_test(struct kunit *test)
+{
+	struct spe_parser_state st = {
+		.win = (u8 *)stream_store,
+		.win_size = sizeof(stream_store) - 1,
+	};
+	struct spe_record rec;
+	int ret;
+
+	ret = spe_parse_one_record(&st, &rec);
+	KUNIT_ASSERT_EQ(test, SPE_PARSE_NEED_MORE, ret);
+	KUNIT_EXPECT_EQ(test, 0UL, st.aux_tail);
+	KUNIT_EXPECT_EQ(test, 0UL, st.bytes);
+
+	/* The next AUX copy starts at the unchanged tail and includes END. */
+	st.win_size = sizeof(stream_store);
+	ret = spe_parse_one_record(&st, &rec);
+	KUNIT_ASSERT_EQ(test, SPE_PARSE_REPORT, ret);
+	KUNIT_EXPECT_EQ(test, (unsigned long)sizeof(stream_store),
+			st.aux_tail);
+	KUNIT_EXPECT_EQ(test, 0x1000UL, rec.va);
+	KUNIT_EXPECT_EQ(test, 42U, rec.tid);
+}
+
+static struct kunit_case spe_parser_test_cases[] = {
+	KUNIT_CASE_PARAM(spe_parse_case_test, spe_parse_gen_params),
+	KUNIT_CASE(spe_split_record_test),
+	{},
+};
+
+static struct kunit_suite spe_parser_test_suite = {
+	.name = "damon_perf_spe_parser",
+	.test_cases = spe_parser_test_cases,
+};
+
+kunit_test_suite(spe_parser_test_suite);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.