new file mode 100644
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for damon selftests
+
+TEST_FILES = _chk_dependency.sh _chk_record_file.py
+TEST_PROGS = debugfs_attrs.sh debugfs_record.sh
+
+include ../lib.mk
new file mode 100644
@@ -0,0 +1,28 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+DBGFS=/sys/kernel/debug/damon
+
+if [ $EUID -ne 0 ];
+then
+ echo "Run as root"
+ exit $ksft_skip
+fi
+
+if [ ! -d $DBGFS ]
+then
+ echo "$DBGFS not found"
+ exit $ksft_skip
+fi
+
+for f in attrs record target_ids monitor_on
+do
+ if [ ! -f "$DBGFS/$f" ]
+ then
+ echo "$f not found"
+ exit 1
+ fi
+done
new file mode 100644
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"Check whether the DAMON record file is valid"
+
+import argparse
+import struct
+import sys
+
+fmt_version = 0
+
+def set_fmt_version(f):
+ global fmt_version
+
+ mark = f.read(16)
+ if mark == b'damon_recfmt_ver':
+ fmt_version = struct.unpack('i', f.read(4))[0]
+ else:
+ fmt_version = 0
+ f.seek(0)
+ return fmt_version
+
+def read_pid(f):
+ if fmt_version == 1:
+ pid = struct.unpack('i', f.read(4))[0]
+ else:
+ pid = struct.unpack('L', f.read(8))[0]
+
+def err_percent(val, expected):
+ return abs(val - expected) / expected * 100
+
+def chk_task_info(f):
+ pid = read_pid(f)
+ nr_regions = struct.unpack('I', f.read(4))[0]
+
+ if nr_regions > max_nr_regions:
+ print('too many regions: %d > %d' % (nr_regions, max_nr_regions))
+ exit(1)
+
+ nr_gaps = 0
+ eaddr = 0
+ for r in range(nr_regions):
+ saddr = struct.unpack('L', f.read(8))[0]
+ if eaddr and saddr != eaddr:
+ nr_gaps += 1
+ eaddr = struct.unpack('L', f.read(8))[0]
+ nr_accesses = struct.unpack('I', f.read(4))[0]
+
+ if saddr >= eaddr:
+ print('wrong region [%d,%d)' % (saddr, eaddr))
+ exit(1)
+
+ max_nr_accesses = aint / sint
+ if nr_accesses > max_nr_accesses:
+ if err_percent(nr_accesses, max_nr_accesses) > 15:
+ print('too high nr_access: expected %d but %d' %
+ (max_nr_accesses, nr_accesses))
+ exit(1)
+ if nr_gaps != 2:
+ print('number of gaps are not two but %d' % nr_gaps)
+ exit(1)
+
+def parse_time_us(bindat):
+ sec = struct.unpack('l', bindat[0:8])[0]
+ nsec = struct.unpack('l', bindat[8:16])[0]
+ return (sec * 1000000000 + nsec) / 1000
+
+def main():
+ global sint
+ global aint
+ global min_nr
+ global max_nr_regions
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('file', metavar='<file>',
+ help='path to the record file')
+ parser.add_argument('--attrs', metavar='<attrs>',
+ default='5000 100000 1000000 10 1000',
+ help='content of debugfs attrs file')
+ args = parser.parse_args()
+ file_path = args.file
+ attrs = [int(x) for x in args.attrs.split()]
+ sint, aint, rint, min_nr, max_nr_regions = attrs
+
+ with open(file_path, 'rb') as f:
+ set_fmt_version(f)
+ last_aggr_time = None
+ while True:
+ timebin = f.read(16)
+ if len(timebin) != 16:
+ break
+
+ now = parse_time_us(timebin)
+ if not last_aggr_time:
+ last_aggr_time = now
+ else:
+ error = err_percent(now - last_aggr_time, aint)
+ if error > 15:
+ print('wrong aggr interval: expected %d, but %d' %
+ (aint, now - last_aggr_time))
+ exit(1)
+ last_aggr_time = now
+
+ nr_tasks = struct.unpack('I', f.read(4))[0]
+ for t in range(nr_tasks):
+ chk_task_info(f)
+
+if __name__ == '__main__':
+ main()
new file mode 100755
@@ -0,0 +1,161 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source ./_chk_dependency.sh
+
+# Test attrs file
+file="$DBGFS/attrs"
+
+ORIG_CONTENT=$(cat $file)
+
+echo 1 2 3 4 5 > $file
+if [ $? -ne 0 ]
+then
+ echo "$file write failed"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo 1 2 3 4 > $file
+if [ $? -eq 0 ]
+then
+ echo "$file write success (should failed)"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+CONTENT=$(cat $file)
+if [ "$CONTENT" != "1 2 3 4 5" ]
+then
+ echo "$file not written"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo $ORIG_CONTENT > $file
+
+# Test record file
+file="$DBGFS/record"
+
+ORIG_CONTENT=$(cat $file)
+
+echo "4242 foo.bar" > $file
+if [ $? -ne 0 ]
+then
+ echo "$file writing sane input failed"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo abc 2 3 > $file
+if [ $? -eq 0 ]
+then
+ echo "$file writing insane input 1 success (should failed)"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo 123 > $file
+if [ $? -eq 0 ]
+then
+ echo "$file writing insane input 2 success (should failed)"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+CONTENT=$(cat $file)
+if [ "$CONTENT" != "4242 foo.bar" ]
+then
+ echo "$file not written"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo "0 null" > $file
+if [ $? -ne 0 ]
+then
+ echo "$file disabling write fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+CONTENT=$(cat $file)
+if [ "$CONTENT" != "0 null" ]
+then
+ echo "$file not disabled"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo "4242 foo.bar" > $file
+if [ $? -ne 0 ]
+then
+ echo "$file writing sane data again fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo $ORIG_CONTENT > $file
+
+# Test target_ids file
+file="$DBGFS/target_ids"
+
+ORIG_CONTENT=$(cat $file)
+
+echo "1 2 3 4" > $file
+if [ $? -ne 0 ]
+then
+ echo "$file write fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo "1 2 abc 4" > $file
+if [ $? -ne 0 ]
+then
+ echo "$file write fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+CONTENT=$(cat $file)
+if [ "$CONTENT" != "1 2" ]
+then
+ echo "$file not written"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo abc 2 3 > $file
+if [ $? -ne 0 ]
+then
+ echo "$file wrong value write fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+if [ ! -z "$(cat $file)" ]
+then
+ echo "$file not cleared"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo > $file
+if [ $? -ne 0 ]
+then
+ echo "$file init fail"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+if [ ! -z "$(cat $file)" ]
+then
+ echo "$file not initialized"
+ echo $ORIG_CONTENT > $file
+ exit 1
+fi
+
+echo $ORIG_CONTENT > $file
+
+echo "PASS"
new file mode 100755
@@ -0,0 +1,50 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source ./_chk_dependency.sh
+
+restore_attrs()
+{
+ echo $ORIG_ATTRS > $DBGFS/attrs
+ echo $ORIG_TARGET_IDS > $DBGFS/target_ids
+ echo $ORIG_RECORD > $DBGFS/record
+}
+
+ORIG_ATTRS=$(cat $DBGFS/attrs)
+ORIG_TARGET_IDS=$(cat $DBGFS/target_ids)
+ORIG_RECORD=$(cat $DBGFS/record)
+
+rfile=$pwd/damon.data
+
+rm -f $rfile
+ATTRS="5000 100000 1000000 10 1000"
+echo $ATTRS > $DBGFS/attrs
+echo 4096 $rfile > $DBGFS/record
+sleep 5 &
+echo $(pidof sleep) > $DBGFS/target_ids
+echo on > $DBGFS/monitor_on
+sleep 0.5
+killall sleep
+echo off > $DBGFS/monitor_on
+
+sync
+
+if [ ! -f $rfile ]
+then
+ echo "record file not made"
+ restore_attrs
+
+ exit 1
+fi
+
+python3 ./_chk_record.py $rfile --attrs "$ATTRS"
+if [ $? -ne 0 ]
+then
+ echo "record file is wrong"
+ restore_attrs
+ exit 1
+fi
+
+rm -f $rfile
+restore_attrs
+echo "PASS"