Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
access_memory
access_memory_even
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for damon selftests
TEST_GEN_FILES += access_memory access_memory_even
TEST_FILES = _damon_sysfs.py
TEST_FILES += drgn_dump_damon_status.py
TEST_FILES += _common.sh
# functionality tests
TEST_PROGS += sysfs.sh
TEST_PROGS += sysfs.py
TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += damos_quota.py damos_quota_goal.py damos_apply_interval.py
TEST_PROGS += damos_tried_regions.py damon_nr_regions.py
TEST_PROGS += reclaim.sh lru_sort.sh
# regression tests (reproducers of previously found bugs)
TEST_PROGS += sysfs_update_removed_scheme_dir.sh
TEST_PROGS += sysfs_update_schemes_tried_regions_hang.py
TEST_PROGS += sysfs_memcg_path_leak.sh
TEST_PROGS += sysfs_no_op_commit_break.py
EXTRA_CLEAN = __pycache__
include ../lib.mk
@@ -0,0 +1,11 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
check_dependencies()
{
if [ $EUID -ne 0 ]
then
echo "Run as root"
exit $ksft_skip
fi
}
@@ -0,0 +1,868 @@
# SPDX-License-Identifier: GPL-2.0
import os
ksft_skip=4
sysfs_root = None
with open('/proc/mounts', 'r') as f:
for line in f:
dev_name, mount_point, dev_fs = line.split()[:3]
if dev_fs == 'sysfs':
sysfs_root = '%s/kernel/mm/damon/admin' % mount_point
break
if sysfs_root is None:
print('Seems sysfs not mounted?')
exit(ksft_skip)
if not os.path.exists(sysfs_root):
print('Seems DAMON disabled?')
exit(ksft_skip)
def write_file(path, string):
"Returns error string if failed, or None otherwise"
string = '%s' % string
try:
with open(path, 'w') as f:
f.write(string)
except Exception as e:
return '%s' % e
return None
def read_file(path):
'''Returns the read content and error string. The read content is None if
the reading failed'''
try:
with open(path, 'r') as f:
return f.read(), None
except Exception as e:
return None, '%s' % e
class DamosAccessPattern:
size = None
nr_accesses = None
age = None
scheme = None
def __init__(self, size=None, nr_accesses=None, age=None):
self.size = size
self.nr_accesses = nr_accesses
self.age = age
if self.size is None:
self.size = [0, 2**64 - 1]
if self.nr_accesses is None:
self.nr_accesses = [0, 2**32 - 1]
if self.age is None:
self.age = [0, 2**32 - 1]
def sysfs_dir(self):
return os.path.join(self.scheme.sysfs_dir(), 'access_pattern')
def stage(self):
err = write_file(
os.path.join(self.sysfs_dir(), 'sz', 'min'), self.size[0])
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'sz', 'max'), self.size[1])
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'min'),
self.nr_accesses[0])
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'max'),
self.nr_accesses[1])
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'age', 'min'), self.age[0])
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'age', 'max'), self.age[1])
if err is not None:
return err
qgoal_metric_user_input = 'user_input'
qgoal_metric_some_mem_psi_us = 'some_mem_psi_us'
qgoal_metrics = [qgoal_metric_user_input, qgoal_metric_some_mem_psi_us]
class DamosQuotaGoal:
metric = None
target_value = None
current_value = None
nid = None
effective_bytes = None
quota = None # owner quota
idx = None
def __init__(self, metric, target_value=10000, current_value=0, nid=0):
self.metric = metric
self.target_value = target_value
self.current_value = current_value
self.nid = nid
def sysfs_dir(self):
return os.path.join(self.quota.sysfs_dir(), 'goals', '%d' % self.idx)
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'target_metric'),
self.metric)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'target_value'),
self.target_value)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'current_value'),
self.current_value)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'nid'), self.nid)
if err is not None:
return err
return None
class DamosQuota:
sz = None # size quota, in bytes
ms = None # time quota
goals = None # quota goals
goal_tuner = None # quota goal tuner
reset_interval_ms = None # quota reset interval
fail_charge_num = None
fail_charge_denom = None
weight_sz_permil = None
weight_nr_accesses_permil = None
weight_age_permil = None
scheme = None # owner scheme
def __init__(self, sz=0, ms=0, goals=None, goal_tuner='consist',
reset_interval_ms=0, fail_charge_num=0, fail_charge_denom=0,
weight_sz_permil=0, weight_nr_accesses_permil=0,
weight_age_permil=0):
self.sz = sz
self.ms = ms
self.reset_interval_ms = reset_interval_ms
self.weight_sz_permil = weight_sz_permil
self.weight_nr_accesses_permil = weight_nr_accesses_permil
self.weight_age_permil = weight_age_permil
self.goals = goals if goals is not None else []
self.goal_tuner = goal_tuner
for idx, goal in enumerate(self.goals):
goal.idx = idx
goal.quota = self
self.fail_charge_num = fail_charge_num
self.fail_charge_denom = fail_charge_denom
def sysfs_dir(self):
return os.path.join(self.scheme.sysfs_dir(), 'quotas')
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'),
self.reset_interval_ms)
if err is not None:
return err
err = write_file(os.path.join(
self.sysfs_dir(), 'weights', 'sz_permil'), self.weight_sz_permil)
if err is not None:
return err
err = write_file(os.path.join(
self.sysfs_dir(), 'weights', 'nr_accesses_permil'),
self.weight_nr_accesses_permil)
if err is not None:
return err
err = write_file(os.path.join(
self.sysfs_dir(), 'weights', 'age_permil'), self.weight_age_permil)
if err is not None:
return err
nr_goals_file = os.path.join(self.sysfs_dir(), 'goals', 'nr_goals')
content, err = read_file(nr_goals_file)
if err is not None:
return err
if int(content) != len(self.goals):
err = write_file(nr_goals_file, len(self.goals))
if err is not None:
return err
for goal in self.goals:
err = goal.stage()
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'goal_tuner'), self.goal_tuner)
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'fail_charge_num'),
self.fail_charge_num)
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'fail_charge_denom'),
self.fail_charge_denom)
if err is not None:
return err
return None
class DamosWatermarks:
metric = None
interval = None
high = None
mid = None
low = None
scheme = None # owner scheme
def __init__(self, metric='none', interval=0, high=0, mid=0, low=0):
self.metric = metric
self.interval = interval
self.high = high
self.mid = mid
self.low = low
def sysfs_dir(self):
return os.path.join(self.scheme.sysfs_dir(), 'watermarks')
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'metric'), self.metric)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'interval_us'),
self.interval)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'high'), self.high)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'mid'), self.mid)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'low'), self.low)
if err is not None:
return err
class DamosFilter:
type_ = None
matching = None
allow = None
memcg_path = None
addr_start = None
addr_end = None
target_idx = None
min_ = None
max_ = None
idx = None
filters = None # owner filters
def __init__(self, type_='anon', matching=False, allow=False,
memcg_path='', addr_start=0, addr_end=0, target_idx=0, min_=0,
max_=0):
self.type_ = type_
self.matching = matching
self.allow = allow
self.memcg_path = memcg_path,
self.addr_start = addr_start
self.addr_end = addr_end
self.target_idx = target_idx
self.min_ = min_
self.max_ = max_
def sysfs_dir(self):
return os.path.join(self.filters.sysfs_dir(), '%d' % self.idx)
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'type'), self.type_)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'matching'),
self.matching)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'allow'), self.allow)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'memcg_path'),
self.memcg_path)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'addr_start'),
self.addr_start)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'addr_end'),
self.addr_end)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'damon_target_idx'),
self.target_idx)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'min'), self.min_)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'max'), self.max_)
if err is not None:
return err
return None
class DamosFilters:
name = None
filters = None
scheme = None # owner scheme
def __init__(self, name, filters=[]):
self.name = name
self.filters = filters
for idx, filter_ in enumerate(self.filters):
filter_.idx = idx
filter_.filters = self
def sysfs_dir(self):
return os.path.join(self.scheme.sysfs_dir(), self.name)
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'nr_filters'),
len(self.filters))
if err is not None:
return err
for filter_ in self.filters:
err = filter_.stage()
if err is not None:
return err
return None
class DamosDest:
id = None
weight = None
idx = None
dests = None # owner dests
def __init__(self, id=0, weight=0):
self.id = id
self.weight = weight
def sysfs_dir(self):
return os.path.join(self.dests.sysfs_dir(), '%d' % self.idx)
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'id'), self.id)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'weight'), self.weight)
if err is not None:
return err
return None
class DamosDests:
dests = None
scheme = None # owner scheme
def __init__(self, dests=[]):
self.dests = dests
for idx, dest in enumerate(self.dests):
dest.idx = idx
dest.dests = self
def sysfs_dir(self):
return os.path.join(self.scheme.sysfs_dir(), 'dests')
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'nr_dests'),
len(self.dests))
if err is not None:
return err
for dest in self.dests:
err = dest.stage()
if err is not None:
return err
return None
class DamosStats:
nr_tried = None
sz_tried = None
nr_applied = None
sz_applied = None
qt_exceeds = None
def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds):
self.nr_tried = nr_tried
self.sz_tried = sz_tried
self.nr_applied = nr_applied
self.sz_applied = sz_applied
self.qt_exceeds = qt_exceeds
class DamosTriedRegion:
def __init__(self, start, end, nr_accesses, age):
self.start = start
self.end = end
self.nr_accesses = nr_accesses
self.age = age
class Damos:
action = None
access_pattern = None
quota = None
watermarks = None
core_filters = None
ops_filters = None
filters = None
apply_interval_us = None
target_nid = None
dests = None
idx = None
context = None
tried_bytes = None
stats = None
tried_regions = None
def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
quota=DamosQuota(), watermarks=DamosWatermarks(),
core_filters=[], ops_filters=[], filters=[], target_nid=0,
dests=DamosDests(), apply_interval_us=0):
self.action = action
self.access_pattern = access_pattern
self.access_pattern.scheme = self
self.quota = quota
self.quota.scheme = self
self.watermarks = watermarks
self.watermarks.scheme = self
self.core_filters = DamosFilters(name='core_filters',
filters=core_filters)
self.core_filters.scheme = self
self.ops_filters = DamosFilters(name='ops_filters',
filters=ops_filters)
self.ops_filters.scheme = self
self.filters = DamosFilters(name='filters', filters=filters)
self.filters.scheme = self
self.target_nid = target_nid
self.dests = dests
self.dests.scheme = self
self.apply_interval_us = apply_interval_us
def sysfs_dir(self):
return os.path.join(
self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
def stage(self):
err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
if err is not None:
return err
err = self.access_pattern.stage()
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'),
'%d' % self.apply_interval_us)
if err is not None:
return err
err = self.quota.stage()
if err is not None:
return err
err = self.watermarks.stage()
if err is not None:
return err
err = self.core_filters.stage()
if err is not None:
return err
err = self.ops_filters.stage()
if err is not None:
return err
err = self.filters.stage()
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'target_nid'), '%d' %
self.target_nid)
if err is not None:
return err
err = self.dests.stage()
if err is not None:
return err
class DamonTarget:
pid = None
obsolete = None
# todo: Support target regions if test is made
idx = None
context = None
def __init__(self, pid, obsolete=False):
self.pid = pid
self.obsolete = obsolete
def sysfs_dir(self):
return os.path.join(
self.context.sysfs_dir(), 'targets', '%d' % self.idx)
def stage(self):
err = write_file(
os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
if err is not None:
return err
err = write_file(
os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
if err is not None:
return err
return write_file(
os.path.join(self.sysfs_dir(), 'obsolete_target'),
'Y' if self.obsolete else 'N')
class IntervalsGoal:
access_bp = None
aggrs = None
min_sample_us = None
max_sample_us = None
attrs = None # owner DamonAttrs
def __init__(self, access_bp=0, aggrs=0, min_sample_us=0, max_sample_us=0):
self.access_bp = access_bp
self.aggrs = aggrs
self.min_sample_us = min_sample_us
self.max_sample_us = max_sample_us
def sysfs_dir(self):
return os.path.join(self.attrs.interval_sysfs_dir(), 'intervals_goal')
def stage(self):
err = write_file(
os.path.join(self.sysfs_dir(), 'access_bp'), self.access_bp)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'aggrs'), self.aggrs)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'min_sample_us'),
self.min_sample_us)
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'max_sample_us'),
self.max_sample_us)
if err is not None:
return err
return None
class DamonAttrs:
sample_us = None
aggr_us = None
intervals_goal = None
update_us = None
min_nr_regions = None
max_nr_regions = None
context = None
def __init__(self, sample_us=5000, aggr_us=100000,
intervals_goal=IntervalsGoal(), update_us=1000000,
min_nr_regions=10, max_nr_regions=1000):
self.sample_us = sample_us
self.aggr_us = aggr_us
self.intervals_goal = intervals_goal
self.intervals_goal.attrs = self
self.update_us = update_us
self.min_nr_regions = min_nr_regions
self.max_nr_regions = max_nr_regions
def interval_sysfs_dir(self):
return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
'intervals')
def nr_regions_range_sysfs_dir(self):
return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
'nr_regions')
def stage(self):
err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
self.sample_us)
if err is not None:
return err
err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
self.aggr_us)
if err is not None:
return err
err = self.intervals_goal.stage()
if err is not None:
return err
err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
self.update_us)
if err is not None:
return err
err = write_file(
os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
self.min_nr_regions)
if err is not None:
return err
err = write_file(
os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
self.max_nr_regions)
if err is not None:
return err
class DamonCtx:
ops = None
monitoring_attrs = None
targets = None
schemes = None
kdamond = None
pause = None
idx = None
def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
schemes=[], pause=False):
self.ops = ops
self.monitoring_attrs = monitoring_attrs
self.monitoring_attrs.context = self
self.targets = targets
for idx, target in enumerate(self.targets):
target.idx = idx
target.context = self
self.schemes = schemes
for idx, scheme in enumerate(self.schemes):
scheme.idx = idx
scheme.context = self
self.pause=pause
def sysfs_dir(self):
return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
'%d' % self.idx)
def stage(self):
err = write_file(
os.path.join(self.sysfs_dir(), 'operations'), self.ops)
if err is not None:
return err
err = self.monitoring_attrs.stage()
if err is not None:
return err
nr_targets_file = os.path.join(
self.sysfs_dir(), 'targets', 'nr_targets')
content, err = read_file(nr_targets_file)
if err is not None:
return err
if int(content) != len(self.targets):
err = write_file(nr_targets_file, '%d' % len(self.targets))
if err is not None:
return err
for target in self.targets:
err = target.stage()
if err is not None:
return err
nr_schemes_file = os.path.join(
self.sysfs_dir(), 'schemes', 'nr_schemes')
content, err = read_file(nr_schemes_file)
if err is not None:
return err
if int(content) != len(self.schemes):
err = write_file(nr_schemes_file, '%d' % len(self.schemes))
if err is not None:
return err
for scheme in self.schemes:
err = scheme.stage()
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'pause'), self.pause)
if err is not None:
return err
return None
class Kdamond:
state = None
pid = None
contexts = None
idx = None # index of this kdamond between siblings
kdamonds = None # parent
def __init__(self, contexts=[]):
self.contexts = contexts
for idx, context in enumerate(self.contexts):
context.idx = idx
context.kdamond = self
def sysfs_dir(self):
return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
def start(self):
nr_contexts_file = os.path.join(self.sysfs_dir(),
'contexts', 'nr_contexts')
content, err = read_file(nr_contexts_file)
if err is not None:
return err
if int(content) != len(self.contexts):
err = write_file(nr_contexts_file, '%d' % len(self.contexts))
if err is not None:
return err
for context in self.contexts:
err = context.stage()
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
if err is not None:
return err
self.pid, err = read_file(os.path.join(self.sysfs_dir(), 'pid'))
return err
def stop(self):
err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off')
return err
def update_schemes_tried_regions(self):
err = write_file(os.path.join(self.sysfs_dir(), 'state'),
'update_schemes_tried_regions')
if err is not None:
return err
for context in self.contexts:
for scheme in context.schemes:
tried_regions = []
tried_regions_dir = os.path.join(
scheme.sysfs_dir(), 'tried_regions')
region_indices = []
for filename in os.listdir(
os.path.join(scheme.sysfs_dir(), 'tried_regions')):
tried_region_dir = os.path.join(tried_regions_dir, filename)
if not os.path.isdir(tried_region_dir):
continue
region_indices.append(int(filename))
for region_idx in sorted(region_indices):
tried_region_dir = os.path.join(tried_regions_dir,
'%d' % region_idx)
region_values = []
for f in ['start', 'end', 'nr_accesses', 'age']:
content, err = read_file(
os.path.join(tried_region_dir, f))
if err is not None:
return err
region_values.append(int(content))
tried_regions.append(DamosTriedRegion(*region_values))
scheme.tried_regions = tried_regions
def update_schemes_tried_bytes(self):
err = write_file(os.path.join(self.sysfs_dir(), 'state'),
'update_schemes_tried_bytes')
if err is not None:
return err
for context in self.contexts:
for scheme in context.schemes:
content, err = read_file(os.path.join(scheme.sysfs_dir(),
'tried_regions', 'total_bytes'))
if err is not None:
return err
scheme.tried_bytes = int(content)
def update_schemes_stats(self):
err = write_file(os.path.join(self.sysfs_dir(), 'state'),
'update_schemes_stats')
if err is not None:
return err
for context in self.contexts:
for scheme in context.schemes:
stat_values = []
for stat in ['nr_tried', 'sz_tried', 'nr_applied',
'sz_applied', 'qt_exceeds']:
content, err = read_file(
os.path.join(scheme.sysfs_dir(), 'stats', stat))
if err is not None:
return err
stat_values.append(int(content))
scheme.stats = DamosStats(*stat_values)
def update_schemes_effective_quotas(self):
err = write_file(os.path.join(self.sysfs_dir(), 'state'),
'update_schemes_effective_quotas')
if err is not None:
return err
for context in self.contexts:
for scheme in context.schemes:
for goal in scheme.quota.goals:
content, err = read_file(
os.path.join(scheme.quota.sysfs_dir(),
'effective_bytes'))
if err is not None:
return err
goal.effective_bytes = int(content)
return None
def commit(self):
nr_contexts_file = os.path.join(self.sysfs_dir(),
'contexts', 'nr_contexts')
content, err = read_file(nr_contexts_file)
if err is not None:
return err
if int(content) != len(self.contexts):
err = write_file(nr_contexts_file, '%d' % len(self.contexts))
if err is not None:
return err
for context in self.contexts:
err = context.stage()
if err is not None:
return err
err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit')
return err
def commit_schemes_quota_goals(self):
for context in self.contexts:
for scheme in context.schemes:
for goal in scheme.quota.goals:
err = goal.stage()
if err is not None:
print('commit_schemes_quota_goals failed stagign: %s'%
err)
exit(1)
return write_file(os.path.join(self.sysfs_dir(), 'state'),
'commit_schemes_quota_goals')
class Kdamonds:
kdamonds = []
def __init__(self, kdamonds=[]):
self.kdamonds = kdamonds
for idx, kdamond in enumerate(self.kdamonds):
kdamond.idx = idx
kdamond.kdamonds = self
def sysfs_dir(self):
return os.path.join(sysfs_root, 'kdamonds')
def start(self):
err = write_file(os.path.join(self.sysfs_dir(), 'nr_kdamonds'),
'%s' % len(self.kdamonds))
if err is not None:
return err
for kdamond in self.kdamonds:
err = kdamond.start()
if err is not None:
return err
return None
def stop(self):
for kdamond in self.kdamonds:
err = kdamond.stop()
if err is not None:
return err
return None
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Artificial memory access program for testing DAMON.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
enum access_mode {
ACCESS_MODE_ONCE,
ACCESS_MODE_REPEAT,
};
int main(int argc, char *argv[])
{
char **regions;
clock_t start_clock;
int nr_regions;
int sz_region;
int access_time_ms;
enum access_mode mode = ACCESS_MODE_ONCE;
int i;
if (argc < 4) {
printf("Usage: %s <number> <size (bytes)> <time (ms)> [mode]\n",
argv[0]);
return -1;
}
nr_regions = atoi(argv[1]);
sz_region = atoi(argv[2]);
access_time_ms = atoi(argv[3]);
if (argc > 4 && !strcmp(argv[4], "repeat"))
mode = ACCESS_MODE_REPEAT;
regions = malloc(sizeof(*regions) * nr_regions);
for (i = 0; i < nr_regions; i++)
regions[i] = malloc(sz_region);
do {
for (i = 0; i < nr_regions; i++) {
start_clock = clock();
while ((clock() - start_clock) * 1000 / CLOCKS_PER_SEC
< access_time_ms)
memset(regions[i], i, sz_region);
}
} while (mode == ACCESS_MODE_REPEAT);
return 0;
}
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Artificial memory access program for testing DAMON.
*
* Receives number of regions and size of each region from user. Allocate the
* regions and repeatedly access even numbered (starting from zero) regions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char **regions;
int nr_regions;
int sz_region;
int i;
if (argc != 3) {
printf("Usage: %s <number> <size (bytes)>\n", argv[0]);
return -1;
}
nr_regions = atoi(argv[1]);
sz_region = atoi(argv[2]);
regions = malloc(sizeof(*regions) * nr_regions);
for (i = 0; i < nr_regions; i++)
regions[i] = malloc(sz_region);
while (1) {
for (i = 0; i < nr_regions; i++) {
if (i % 2 == 0)
memset(regions[i], i, sz_region);
}
}
return 0;
}
@@ -0,0 +1,7 @@
CONFIG_DAMON=y
CONFIG_DAMON_SYSFS=y
CONFIG_DAMON_PADDR=y
CONFIG_DAMON_VADDR=y
CONFIG_DAMON_RECLAIM=y
CONFIG_DAMON_LRU_SORT=y
CONFIG_DAMON_DEBUG_SANITY=y
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
'''
Create process of the given 'real_nr_regions' regions, monitor it using
DAMON with given '{min,max}_nr_regions' monitoring parameter.
Exit with non-zero return code if the given {min,max}_nr_regions is not
kept.
'''
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory_even', '%d' % real_nr_regions,
'%d' % sz_region])
# stat every monitored regions
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
monitoring_attrs=_damon_sysfs.DamonAttrs(
min_nr_regions=min_nr_regions,
max_nr_regions=max_nr_regions),
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(action='stat',
)] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err is not None:
proc.terminate()
print('kdamond start failed: %s' % err)
exit(1)
collected_nr_regions = []
while proc.poll() is None:
time.sleep(0.1)
err = kdamonds.kdamonds[0].update_schemes_tried_regions()
if err is not None:
proc.terminate()
print('tried regions update failed: %s' % err)
exit(1)
scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
if scheme.tried_regions is None:
proc.terminate()
print('tried regions is not collected')
exit(1)
nr_tried_regions = len(scheme.tried_regions)
if nr_tried_regions <= 0:
proc.terminate()
print('tried regions is not created')
exit(1)
collected_nr_regions.append(nr_tried_regions)
if len(collected_nr_regions) > 10:
break
proc.terminate()
kdamonds.stop()
test_name = 'nr_regions test with %d/%d/%d real/min/max nr_regions' % (
real_nr_regions, min_nr_regions, max_nr_regions)
collected_nr_regions.sort()
if (collected_nr_regions[0] < min_nr_regions or
collected_nr_regions[-1] > max_nr_regions):
print('fail %s' % test_name)
print('number of regions that collected are:')
for nr in collected_nr_regions:
print(nr)
exit(1)
print('pass %s ' % test_name)
def main():
# test min_nr_regions larger than real nr regions
test_nr_regions(10, 20, 100)
# test max_nr_regions smaller than real nr regions
test_nr_regions(15, 3, 10)
# test online-tuned max_nr_regions that smaller than real nr regions
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory_even', '14', '%d' % sz_region])
# stat every monitored regions
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
monitoring_attrs=_damon_sysfs.DamonAttrs(
min_nr_regions=10, max_nr_regions=1000),
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(action='stat',
)] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err is not None:
proc.terminate()
print('kdamond start failed: %s' % err)
exit(1)
# wait until the real regions are found
time.sleep(3)
attrs = kdamonds.kdamonds[0].contexts[0].monitoring_attrs
attrs.min_nr_regions = 3
attrs.max_nr_regions = 7
attrs.update_us = 100000
err = kdamonds.kdamonds[0].commit()
if err is not None:
proc.terminate()
print('commit failed: %s' % err)
exit(1)
# wait for next merge operation is executed
time.sleep(0.3)
err = kdamonds.kdamonds[0].update_schemes_tried_regions()
if err is not None:
proc.terminate()
print('tried regions update failed: %s' % err)
exit(1)
scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
if scheme.tried_regions is None:
proc.terminate()
print('tried regions is not collected')
exit(1)
nr_tried_regions = len(scheme.tried_regions)
if nr_tried_regions <= 0:
proc.terminate()
print('tried regions is not created')
exit(1)
proc.terminate()
if nr_tried_regions > 7:
print('fail online-tuned max_nr_regions: %d > 7' % nr_tried_regions)
exit(1)
print('pass online-tuned max_nr_regions')
if __name__ == '__main__':
main()
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def main():
# access two 10 MiB memory regions, 2 second per each
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory', '2', '%d' % sz_region, '2000'])
# Set quota up to 1 MiB per 100 ms
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[
_damon_sysfs.Damos(
access_pattern=_damon_sysfs.DamosAccessPattern(
# >= 25% access rate, >= 200ms age
nr_accesses=[5, 20], age=[2, 2**64 - 1]),
# aggregation interval (100 ms) is used
apply_interval_us=0),
# use 10ms apply interval
_damon_sysfs.Damos(
access_pattern=_damon_sysfs.DamosAccessPattern(
# >= 25% access rate, >= 200ms age
nr_accesses=[5, 20], age=[2, 2**64 - 1]),
# explicitly set 10 ms apply interval
apply_interval_us=10 * 1000)
] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err != None:
print('kdamond start failed: %s' % err)
exit(1)
wss_collected = []
nr_quota_exceeds = 0
while proc.poll() == None:
time.sleep(0.1)
err = kdamonds.kdamonds[0].update_schemes_stats()
if err != None:
print('stats update failed: %s' % err)
exit(1)
schemes = kdamonds.kdamonds[0].contexts[0].schemes
nr_tried_stats = [s.stats.nr_tried for s in schemes]
if nr_tried_stats[0] == 0 or nr_tried_stats[1] == 0:
print('scheme(s) are not tried')
exit(1)
# Because the second scheme was having the apply interval that is ten times
# lower than that of the first scheme, the second scheme should be tried
# about ten times more frequently than the first scheme. For possible
# timing errors, check if it was at least nine times more freuqnetly tried.
ratio = nr_tried_stats[1] / nr_tried_stats[0]
if ratio < 9:
print('%d / %d = %f (< 9)' %
(nr_tried_stats[1], nr_tried_stats[0], ratio))
exit(1)
if __name__ == '__main__':
main()
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def main():
# access two 10 MiB memory regions, 2 second per each
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory', '2', '%d' % sz_region, '2000'])
# Set quota up to 1 MiB per 100 ms
sz_quota = 1024 * 1024 # 1 MiB
quota_reset_interval = 100 # 100 ms
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(
access_pattern=_damon_sysfs.DamosAccessPattern(
# >= 25% access rate, >= 200ms age
nr_accesses=[5, 20], age=[2, 2**64 - 1]),
quota=_damon_sysfs.DamosQuota(
sz=sz_quota, reset_interval_ms=quota_reset_interval)
)] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err != None:
print('kdamond start failed: %s' % err)
exit(1)
wss_collected = []
nr_quota_exceeds = 0
while proc.poll() == None:
time.sleep(0.1)
err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
if err != None:
print('tried bytes update failed: %s' % err)
exit(1)
err = kdamonds.kdamonds[0].update_schemes_stats()
if err != None:
print('stats update failed: %s' % err)
exit(1)
scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
wss_collected.append(scheme.tried_bytes)
nr_quota_exceeds = scheme.stats.qt_exceeds
wss_collected.sort()
nr_expected_quota_exceeds = 0
for wss in wss_collected:
if wss > sz_quota:
print('quota is not kept: %s > %s' % (wss, sz_quota))
print('collected samples are as below')
print('\n'.join(['%d' % wss for wss in wss_collected]))
exit(1)
if wss == sz_quota:
nr_expected_quota_exceeds += 1
if nr_quota_exceeds < nr_expected_quota_exceeds:
print('quota is exceeded less than expected: %d < %d' %
(nr_quota_exceeds, nr_expected_quota_exceeds))
exit(1)
if __name__ == '__main__':
main()
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def main():
# access two 10 MiB memory regions, 2 second per each
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory', '2', '%d' % sz_region, '2000'])
goal = _damon_sysfs.DamosQuotaGoal(
metric=_damon_sysfs.qgoal_metric_user_input, target_value=10000)
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(
action='stat',
quota=_damon_sysfs.DamosQuota(
goals=[goal], reset_interval_ms=100),
)] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err != None:
print('kdamond start failed: %s' % err)
exit(1)
score_values_to_test = [0, 15000, 5000, 18000]
while proc.poll() == None:
if len(score_values_to_test) == 0:
time.sleep(0.1)
continue
goal.current_value = score_values_to_test.pop(0)
expect_increase = goal.current_value < goal.target_value
err = kdamonds.kdamonds[0].commit_schemes_quota_goals()
if err is not None:
print('commit_schemes_quota_goals failed: %s' % err)
exit(1)
err = kdamonds.kdamonds[0].update_schemes_effective_quotas()
if err is not None:
print('before-update_schemes_effective_quotas failed: %s' % err)
exit(1)
last_effective_bytes = goal.effective_bytes
time.sleep(0.5)
err = kdamonds.kdamonds[0].update_schemes_effective_quotas()
if err is not None:
print('after-update_schemes_effective_quotas failed: %s' % err)
exit(1)
print('score: %s, effective quota: %d -> %d (%.3fx)' % (
goal.current_value, last_effective_bytes, goal.effective_bytes,
goal.effective_bytes / last_effective_bytes
if last_effective_bytes != 0 else -1.0))
if last_effective_bytes == goal.effective_bytes:
# effective quota was already minimum that cannot be more reduced
if expect_increase is False and last_effective_bytes == 1:
continue
print('efective bytes not changed: %d' % goal.effective_bytes)
exit(1)
increased = last_effective_bytes < goal.effective_bytes
if expect_increase != increased:
print('expectation of increase (%s) != increased (%s)' %
(expect_increase, increased))
exit(1)
last_effective_bytes = goal.effective_bytes
if __name__ == '__main__':
main()
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def main():
# repeatedly access even-numbered ones in 14 regions of 10 MiB size
sz_region = 10 * 1024 * 1024
proc = subprocess.Popen(['./access_memory_even', '14', '%d' % sz_region])
# stat every monitored regions
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(action='stat',
)] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err is not None:
proc.terminate()
print('kdamond start failed: %s' % err)
exit(1)
collected_nr_regions = []
while proc.poll() is None:
time.sleep(0.1)
err = kdamonds.kdamonds[0].update_schemes_tried_regions()
if err is not None:
proc.terminate()
print('tried regions update failed: %s' % err)
exit(1)
scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
if scheme.tried_regions is None:
proc.terminate()
print('tried regions is not collected')
exit(1)
nr_tried_regions = len(scheme.tried_regions)
if nr_tried_regions <= 0:
proc.terminate()
print('tried regions is not created')
exit(1)
collected_nr_regions.append(nr_tried_regions)
if len(collected_nr_regions) > 10:
break
proc.terminate()
collected_nr_regions.sort()
sample = collected_nr_regions[4]
print('50-th percentile nr_regions: %d' % sample)
print('expectation (>= 14) is %s' % 'met' if sample >= 14 else 'not met')
if collected_nr_regions[4] < 14:
print('full nr_regions:')
print('\n'.join(collected_nr_regions))
exit(1)
if __name__ == '__main__':
main()
@@ -0,0 +1,227 @@
#!/usr/bin/env drgn
# SPDX-License-Identifier: GPL-2.0
'''
Read DAMON context data and dump as a json string.
'''
import drgn
from drgn import FaultError, NULL, Object, cast, container_of, execscript, offsetof, reinterpret, sizeof
from drgn.helpers.common import *
from drgn.helpers.linux import *
import json
import sys
if "prog" not in globals():
try:
prog = drgn.get_default_prog()
except drgn.NoDefaultProgramError:
prog = drgn.program_from_kernel()
drgn.set_default_prog(prog)
def to_dict(object, attr_name_converter):
d = {}
for attr_name, converter in attr_name_converter:
d[attr_name] = converter(getattr(object, attr_name))
return d
def ops_to_dict(ops):
return to_dict(ops, [
['id', int],
])
def intervals_goal_to_dict(goal):
return to_dict(goal, [
['access_bp', int],
['aggrs', int],
['min_sample_us', int],
['max_sample_us', int],
])
def attrs_to_dict(attrs):
return to_dict(attrs, [
['sample_interval', int],
['aggr_interval', int],
['ops_update_interval', int],
['intervals_goal', intervals_goal_to_dict],
['min_nr_regions', int],
['max_nr_regions', int],
])
def addr_range_to_dict(addr_range):
return to_dict(addr_range, [
['start', int],
['end', int],
])
def region_to_dict(region):
return to_dict(region, [
['ar', addr_range_to_dict],
['sampling_addr', int],
['nr_accesses', int],
['nr_accesses_bp', int],
['age', int],
])
def regions_to_list(regions):
return [region_to_dict(r)
for r in list_for_each_entry(
'struct damon_region', regions.address_of_(), 'list')]
def target_to_dict(target):
return to_dict(target, [
['pid', int],
['nr_regions', int],
['regions_list', regions_to_list],
['obsolete', bool],
])
def targets_to_list(targets):
return [target_to_dict(t)
for t in list_for_each_entry(
'struct damon_target', targets.address_of_(), 'list')]
def damos_access_pattern_to_dict(pattern):
return to_dict(pattern, [
['min_sz_region', int],
['max_sz_region', int],
['min_nr_accesses', int],
['max_nr_accesses', int],
['min_age_region', int],
['max_age_region', int],
])
def damos_quota_goal_to_dict(goal):
return to_dict(goal, [
['metric', int],
['target_value', int],
['current_value', int],
['last_psi_total', int],
['nid', int],
])
def damos_quota_goals_to_list(goals):
return [damos_quota_goal_to_dict(g)
for g in list_for_each_entry(
'struct damos_quota_goal', goals.address_of_(), 'list')]
def damos_quota_to_dict(quota):
return to_dict(quota, [
['reset_interval', int],
['ms', int], ['sz', int],
['goals', damos_quota_goals_to_list],
['goal_tuner', int],
['esz', int],
['fail_charge_num', int],
['fail_charge_denom', int],
['weight_sz', int],
['weight_nr_accesses', int],
['weight_age', int],
])
def damos_watermarks_to_dict(watermarks):
return to_dict(watermarks, [
['metric', int],
['interval', int],
['high', int], ['mid', int], ['low', int],
])
def damos_migrate_dests_to_dict(dests):
nr_dests = int(dests.nr_dests)
node_id_arr = []
weight_arr = []
for i in range(nr_dests):
node_id_arr.append(int(dests.node_id_arr[i]))
weight_arr.append(int(dests.weight_arr[i]))
return {
'node_id_arr': node_id_arr,
'weight_arr': weight_arr,
'nr_dests': nr_dests,
}
def damos_filter_to_dict(damos_filter):
filter_type_keyword = {
0: 'anon',
1: 'active',
2: 'memcg',
3: 'young',
4: 'hugepage_size',
5: 'unmapped',
6: 'addr',
7: 'target'
}
dict_ = {
'type': filter_type_keyword[int(damos_filter.type)],
'matching': bool(damos_filter.matching),
'allow': bool(damos_filter.allow),
}
type_ = dict_['type']
if type_ == 'memcg':
dict_['memcg_id'] = int(damos_filter.memcg_id)
elif type_ == 'addr':
dict_['addr_range'] = [int(damos_filter.addr_range.start),
int(damos_filter.addr_range.end)]
elif type_ == 'target':
dict_['target_idx'] = int(damos_filter.target_idx)
elif type_ == 'hugeapge_size':
dict_['sz_range'] = [int(damos_filter.sz_range.min),
int(damos_filter.sz_range.max)]
return dict_
def scheme_to_dict(scheme):
dict_ = to_dict(scheme, [
['pattern', damos_access_pattern_to_dict],
['action', int],
['apply_interval_us', int],
['quota', damos_quota_to_dict],
['wmarks', damos_watermarks_to_dict],
['target_nid', int],
['migrate_dests', damos_migrate_dests_to_dict],
])
core_filters = []
for f in list_for_each_entry(
'struct damos_filter', scheme.core_filters.address_of_(), 'list'):
core_filters.append(damos_filter_to_dict(f))
dict_['core_filters'] = core_filters
ops_filters = []
for f in list_for_each_entry(
'struct damos_filter', scheme.ops_filters.address_of_(), 'list'):
ops_filters.append(damos_filter_to_dict(f))
dict_['ops_filters'] = ops_filters
return dict_
def schemes_to_list(schemes):
return [scheme_to_dict(s)
for s in list_for_each_entry(
'struct damos', schemes.address_of_(), 'list')]
def damon_ctx_to_dict(ctx):
return to_dict(ctx, [
['ops', ops_to_dict],
['attrs', attrs_to_dict],
['adaptive_targets', targets_to_list],
['schemes', schemes_to_list],
['pause', bool],
])
def main():
if len(sys.argv) < 3:
print('Usage: %s <kdamond pid> <file>' % sys.argv[0])
exit(1)
pid = int(sys.argv[1])
file_to_store = sys.argv[2]
kthread_data = cast('struct kthread *',
find_task(prog, pid).worker_private).data
ctx = cast('struct damon_ctx *', kthread_data)
status = {'contexts': [damon_ctx_to_dict(ctx)]}
if file_to_store == 'stdout':
print(json.dumps(status, indent=4))
else:
with open(file_to_store, 'w') as f:
json.dump(status, f, indent=4)
if __name__ == '__main__':
main()
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source _common.sh
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
check_dependencies
damon_lru_sort_enabled="/sys/module/damon_lru_sort/parameters/enabled"
if [ ! -f "$damon_lru_sort_enabled" ]
then
echo "No 'enabled' file. Maybe DAMON_LRU_SORT not built"
exit $ksft_skip
fi
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 0 ]
then
echo "Another kdamond is running"
exit $ksft_skip
fi
echo Y > "$damon_lru_sort_enabled"
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 1 ]
then
echo "kdamond is not turned on"
exit 1
fi
echo N > "$damon_lru_sort_enabled"
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 0 ]
then
echo "kdamond is not turned off"
exit 1
fi
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source _common.sh
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
check_dependencies
damon_reclaim_enabled="/sys/module/damon_reclaim/parameters/enabled"
if [ ! -f "$damon_reclaim_enabled" ]
then
echo "No 'enabled' file. Maybe DAMON_RECLAIM not built"
exit $ksft_skip
fi
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 0 ]
then
echo "Another kdamond is running"
exit $ksft_skip
fi
echo Y > "$damon_reclaim_enabled"
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 1 ]
then
echo "kdamond is not turned on"
exit 1
fi
echo N > "$damon_reclaim_enabled"
nr_kdamonds=$(pgrep kdamond | wc -l)
if [ "$nr_kdamonds" -ne 0 ]
then
echo "kdamond is not turned off"
exit 1
fi
+360
View File
@@ -0,0 +1,360 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import json
import os
import subprocess
import _damon_sysfs
def dump_damon_status_dict(pid):
try:
subprocess.check_output(['which', 'drgn'], stderr=subprocess.DEVNULL)
except:
return None, 'drgn not found'
file_dir = os.path.dirname(os.path.abspath(__file__))
dump_script = os.path.join(file_dir, 'drgn_dump_damon_status.py')
rc = subprocess.call(['drgn', dump_script, pid, 'damon_dump_output'],
stderr=subprocess.DEVNULL)
if rc != 0:
return None, 'drgn fail'
try:
with open('damon_dump_output', 'r') as f:
return json.load(f), None
except Exception as e:
return None, 'json.load fail (%s)' % e
kdamonds = None
def fail(expectation, status):
print('unexpected %s' % expectation)
print(json.dumps(status, indent=4))
if kdamonds is not None:
kdamonds.stop()
exit(1)
def assert_true(condition, expectation, status):
if condition is not True:
fail(expectation, status)
def assert_watermarks_committed(watermarks, dump):
wmark_metric_val = {
'none': 0,
'free_mem_rate': 1,
}
assert_true(dump['metric'] == wmark_metric_val[watermarks.metric],
'metric', dump)
assert_true(dump['interval'] == watermarks.interval, 'interval', dump)
assert_true(dump['high'] == watermarks.high, 'high', dump)
assert_true(dump['mid'] == watermarks.mid, 'mid', dump)
assert_true(dump['low'] == watermarks.low, 'low', dump)
def assert_quota_goal_committed(qgoal, dump):
metric_val = {
'user_input': 0,
'some_mem_psi_us': 1,
'node_mem_used_bp': 2,
'node_mem_free_bp': 3,
}
assert_true(dump['metric'] == metric_val[qgoal.metric], 'metric', dump)
assert_true(dump['target_value'] == qgoal.target_value, 'target_value',
dump)
if qgoal.metric == 'user_input':
assert_true(dump['current_value'] == qgoal.current_value,
'current_value', dump)
assert_true(dump['nid'] == qgoal.nid, 'nid', dump)
def assert_quota_committed(quota, dump):
assert_true(dump['reset_interval'] == quota.reset_interval_ms,
'reset_interval', dump)
assert_true(dump['ms'] == quota.ms, 'ms', dump)
assert_true(dump['sz'] == quota.sz, 'sz', dump)
for idx, qgoal in enumerate(quota.goals):
assert_quota_goal_committed(qgoal, dump['goals'][idx])
tuner_val = {
'consist': 0,
'temporal': 1,
}
assert_true(dump['goal_tuner'] == tuner_val[quota.goal_tuner],
'goal_tuner', dump)
assert_true(dump['fail_charge_num'] == quota.fail_charge_num,
'fail_charge_num', dump)
assert_true(dump['fail_charge_denom'] == quota.fail_charge_denom,
'fail_charge_denom', dump)
assert_true(dump['weight_sz'] == quota.weight_sz_permil, 'weight_sz', dump)
assert_true(dump['weight_nr_accesses'] == quota.weight_nr_accesses_permil,
'weight_nr_accesses', dump)
assert_true(
dump['weight_age'] == quota.weight_age_permil, 'weight_age', dump)
def assert_migrate_dests_committed(dests, dump):
assert_true(dump['nr_dests'] == len(dests.dests), 'nr_dests', dump)
for idx, dest in enumerate(dests.dests):
assert_true(dump['node_id_arr'][idx] == dest.id, 'node_id', dump)
assert_true(dump['weight_arr'][idx] == dest.weight, 'weight', dump)
def assert_filter_committed(filter_, dump):
assert_true(filter_.type_ == dump['type'], 'type', dump)
assert_true(filter_.matching == dump['matching'], 'matching', dump)
assert_true(filter_.allow == dump['allow'], 'allow', dump)
# TODO: check memcg_path and memcg_id if type is memcg
if filter_.type_ == 'addr':
assert_true([filter_.addr_start, filter_.addr_end] ==
dump['addr_range'], 'addr_range', dump)
elif filter_.type_ == 'target':
assert_true(filter_.target_idx == dump['target_idx'], 'target_idx',
dump)
elif filter_.type_ == 'hugepage_size':
assert_true([filter_.min_, filter_.max_] == dump['sz_range'],
'sz_range', dump)
def assert_access_pattern_committed(pattern, dump):
assert_true(dump['min_sz_region'] == pattern.size[0], 'min_sz_region',
dump)
assert_true(dump['max_sz_region'] == pattern.size[1], 'max_sz_region',
dump)
assert_true(dump['min_nr_accesses'] == pattern.nr_accesses[0],
'min_nr_accesses', dump)
assert_true(dump['max_nr_accesses'] == pattern.nr_accesses[1],
'max_nr_accesses', dump)
assert_true(dump['min_age_region'] == pattern.age[0], 'min_age_region',
dump)
assert_true(dump['max_age_region'] == pattern.age[1], 'miaxage_region',
dump)
def assert_scheme_committed(scheme, dump):
assert_access_pattern_committed(scheme.access_pattern, dump['pattern'])
action_val = {
'willneed': 0,
'cold': 1,
'pageout': 2,
'hugepage': 3,
'nohugeapge': 4,
'collapse': 5,
'lru_prio': 6,
'lru_deprio': 7,
'migrate_hot': 8,
'migrate_cold': 9,
'stat': 10,
}
assert_true(dump['action'] == action_val[scheme.action], 'action', dump)
assert_true(dump['apply_interval_us'] == scheme. apply_interval_us,
'apply_interval_us', dump)
assert_true(dump['target_nid'] == scheme.target_nid, 'target_nid', dump)
assert_migrate_dests_committed(scheme.dests, dump['migrate_dests'])
assert_quota_committed(scheme.quota, dump['quota'])
assert_watermarks_committed(scheme.watermarks, dump['wmarks'])
# TODO: test filters directory
for idx, f in enumerate(scheme.core_filters.filters):
assert_filter_committed(f, dump['core_filters'][idx])
for idx, f in enumerate(scheme.ops_filters.filters):
assert_filter_committed(f, dump['ops_filters'][idx])
def assert_schemes_committed(schemes, dump):
assert_true(len(schemes) == len(dump), 'len_schemes', dump)
for idx, scheme in enumerate(schemes):
assert_scheme_committed(scheme, dump[idx])
def assert_monitoring_attrs_committed(attrs, dump):
assert_true(dump['sample_interval'] == attrs.sample_us, 'sample_interval',
dump)
assert_true(dump['aggr_interval'] == attrs.aggr_us, 'aggr_interval', dump)
assert_true(dump['intervals_goal']['access_bp'] ==
attrs.intervals_goal.access_bp, 'access_bp',
dump['intervals_goal'])
assert_true(dump['intervals_goal']['aggrs'] == attrs.intervals_goal.aggrs,
'aggrs', dump['intervals_goal'])
assert_true(dump['intervals_goal']['min_sample_us'] ==
attrs.intervals_goal.min_sample_us, 'min_sample_us',
dump['intervals_goal'])
assert_true(dump['intervals_goal']['max_sample_us'] ==
attrs.intervals_goal.max_sample_us, 'max_sample_us',
dump['intervals_goal'])
assert_true(dump['ops_update_interval'] == attrs.update_us,
'ops_update_interval', dump)
assert_true(dump['min_nr_regions'] == attrs.min_nr_regions,
'min_nr_regions', dump)
assert_true(dump['max_nr_regions'] == attrs.max_nr_regions,
'max_nr_regions', dump)
def assert_monitoring_target_committed(target, dump):
# target.pid is the pid "number", while dump['pid'] is 'struct pid'
# pointer, and hence cannot be compared.
assert_true(dump['obsolete'] == target.obsolete, 'target obsolete', dump)
def assert_monitoring_targets_committed(targets, dump):
assert_true(len(targets) == len(dump), 'len_targets', dump)
for idx, target in enumerate(targets):
assert_monitoring_target_committed(target, dump[idx])
def assert_ctx_committed(ctx, dump):
ops_val = {
'vaddr': 0,
'fvaddr': 1,
'paddr': 2,
}
assert_true(dump['ops']['id'] == ops_val[ctx.ops], 'ops_id', dump)
assert_monitoring_attrs_committed(ctx.monitoring_attrs, dump['attrs'])
assert_monitoring_targets_committed(ctx.targets, dump['adaptive_targets'])
assert_schemes_committed(ctx.schemes, dump['schemes'])
assert_true(dump['pause'] == ctx.pause, 'pause', dump)
def assert_ctxs_committed(kdamonds):
ctxs_paused_for_dump = []
kdamonds_paused_for_dump = []
# pause for safe state dumping
for kd in kdamonds.kdamonds:
for ctx in kd.contexts:
if ctx.pause is False:
ctx.pause = True
ctxs_paused_for_dump.append(ctx)
if not kd in kdamonds_paused_for_dump:
kdamonds_paused_for_dump.append(kd)
if kd in kdamonds_paused_for_dump:
err = kd.commit()
if err is not None:
print('pause fail (%s)' % err)
kdamonds.stop()
exit(1)
status, err = dump_damon_status_dict(kdamonds.kdamonds[0].pid)
if err is not None:
print(err)
kdamonds.stop()
exit(1)
# resume contexts paused for safe state dumping
for ctx in ctxs_paused_for_dump:
ctx.pause = False
for kd in kdamonds_paused_for_dump:
err = kd.commit()
if err is not None:
print('resume fail (%s)' % err)
kdamonds.stop()
exit(1)
# restore for comparison
for ctx in ctxs_paused_for_dump:
ctx.pause = True
ctxs = kdamonds.kdamonds[0].contexts
dump = status['contexts']
assert_true(len(ctxs) == len(dump), 'ctxs length', dump)
for idx, ctx in enumerate(ctxs):
assert_ctx_committed(ctx, dump[idx])
# restore for the caller
for kd in kdamonds.kdamonds:
for ctx in kd.contexts:
if ctx in ctxs_paused_for_dump:
ctx.pause = False
def main():
global kdamonds
kdamonds = _damon_sysfs.Kdamonds(
[_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
targets=[_damon_sysfs.DamonTarget(pid=-1)],
schemes=[_damon_sysfs.Damos()],
)])])
err = kdamonds.start()
if err is not None:
print('kdamond start failed: %s' % err)
exit(1)
assert_ctxs_committed(kdamonds)
context = _damon_sysfs.DamonCtx(
monitoring_attrs=_damon_sysfs.DamonAttrs(
sample_us=100000, aggr_us=2000000,
intervals_goal=_damon_sysfs.IntervalsGoal(
access_bp=400, aggrs=3, min_sample_us=5000,
max_sample_us=10000000),
update_us=2000000),
schemes=[_damon_sysfs.Damos(
action='pageout',
access_pattern=_damon_sysfs.DamosAccessPattern(
size=[4096, 2**10],
nr_accesses=[3, 317],
age=[5,71]),
quota=_damon_sysfs.DamosQuota(
sz=100*1024*1024, ms=100,
goals=[_damon_sysfs.DamosQuotaGoal(
metric='node_mem_used_bp',
target_value=9950,
nid=1)],
goal_tuner='temporal',
reset_interval_ms=1500,
fail_charge_num=1,
fail_charge_denom=4096,
weight_sz_permil=20,
weight_nr_accesses_permil=200,
weight_age_permil=1000),
watermarks=_damon_sysfs.DamosWatermarks(
metric = 'free_mem_rate', interval = 500000, # 500 ms
high = 500, mid = 400, low = 50),
target_nid=1,
apply_interval_us=1000000,
dests=_damon_sysfs.DamosDests(
dests=[_damon_sysfs.DamosDest(id=1, weight=30),
_damon_sysfs.DamosDest(id=0, weight=70)]),
core_filters=[
_damon_sysfs.DamosFilter(type_='addr', matching=True,
allow=False, addr_start=42,
addr_end=4242),
],
ops_filters=[
_damon_sysfs.DamosFilter(type_='anon', matching=True,
allow=True),
],
)])
context.idx = 0
context.kdamond = kdamonds.kdamonds[0]
kdamonds.kdamonds[0].contexts = [context]
kdamonds.kdamonds[0].commit()
assert_ctxs_committed(kdamonds)
# test online commitment of minimum context.
context = _damon_sysfs.DamonCtx()
context.idx = 0
context.kdamond = kdamonds.kdamonds[0]
kdamonds.kdamonds[0].contexts = [context]
kdamonds.kdamonds[0].commit()
assert_ctxs_committed(kdamonds)
kdamonds.stop()
# test obsolete_target.
proc1 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc2 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc3 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
kdamonds = _damon_sysfs.Kdamonds(
[_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[
_damon_sysfs.DamonTarget(pid=proc1.pid),
_damon_sysfs.DamonTarget(pid=proc2.pid),
_damon_sysfs.DamonTarget(pid=proc3.pid),
],
schemes=[_damon_sysfs.Damos()],
)])])
err = kdamonds.start()
if err is not None:
print('kdamond start failed: %s' % err)
exit(1)
kdamonds.kdamonds[0].contexts[0].targets[1].obsolete = True
kdamonds.kdamonds[0].contexts[0].pause = True
kdamonds.kdamonds[0].commit()
del kdamonds.kdamonds[0].contexts[0].targets[1]
assert_ctxs_committed(kdamonds)
kdamonds.stop()
if __name__ == '__main__':
main()
+432
View File
@@ -0,0 +1,432 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source _common.sh
# Kselftest frmework requirement - SKIP code is 4.
ksft_skip=4
ensure_write_succ()
{
file=$1
content=$2
reason=$3
if ! echo "$content" > "$file"
then
echo "writing $content to $file failed"
echo "expected success because $reason"
exit 1
fi
}
ensure_write_fail()
{
file=$1
content=$2
reason=$3
if (echo "$content" > "$file") 2> /dev/null
then
echo "writing $content to $file succeed ($fail_reason)"
echo "expected failure because $reason"
exit 1
fi
}
ensure_dir()
{
dir=$1
to_ensure=$2
if [ "$to_ensure" = "exist" ] && [ ! -d "$dir" ]
then
echo "$dir dir is expected but not found"
exit 1
elif [ "$to_ensure" = "not_exist" ] && [ -d "$dir" ]
then
echo "$dir dir is not expected but found"
exit 1
fi
}
ensure_file()
{
file=$1
to_ensure=$2
permission=$3
if [ "$to_ensure" = "exist" ]
then
if [ ! -f "$file" ]
then
echo "$file is expected but not found"
exit 1
fi
perm=$(stat -c "%a" "$file")
if [ ! "$perm" = "$permission" ]
then
echo "$file permission: expected $permission but $perm"
exit 1
fi
elif [ "$to_ensure" = "not_exist" ] && [ -f "$dir" ]
then
echo "$file is not expected but found"
exit 1
fi
}
test_range()
{
range_dir=$1
ensure_dir "$range_dir" "exist"
ensure_file "$range_dir/min" "exist" 600
ensure_file "$range_dir/max" "exist" 600
}
test_tried_regions()
{
tried_regions_dir=$1
ensure_dir "$tried_regions_dir" "exist"
ensure_file "$tried_regions_dir/total_bytes" "exist" "400"
}
test_stats()
{
stats_dir=$1
ensure_dir "$stats_dir" "exist"
for f in nr_tried sz_tried nr_applied sz_applied qt_exceeds
do
ensure_file "$stats_dir/$f" "exist" "400"
done
}
test_filter()
{
filter_dir=$1
ensure_file "$filter_dir/type" "exist" "600"
ensure_write_succ "$filter_dir/type" "anon" "valid input"
ensure_write_succ "$filter_dir/type" "memcg" "valid input"
ensure_write_succ "$filter_dir/type" "addr" "valid input"
ensure_write_succ "$filter_dir/type" "target" "valid input"
ensure_write_fail "$filter_dir/type" "foo" "invalid input"
ensure_file "$filter_dir/matching" "exist" "600"
ensure_file "$filter_dir/memcg_path" "exist" "600"
ensure_file "$filter_dir/addr_start" "exist" "600"
ensure_file "$filter_dir/addr_end" "exist" "600"
ensure_file "$filter_dir/damon_target_idx" "exist" "600"
}
test_filters()
{
filters_dir=$1
ensure_dir "$filters_dir" "exist"
ensure_file "$filters_dir/nr_filters" "exist" "600"
ensure_write_succ "$filters_dir/nr_filters" "1" "valid input"
test_filter "$filters_dir/0"
ensure_write_succ "$filters_dir/nr_filters" "2" "valid input"
test_filter "$filters_dir/0"
test_filter "$filters_dir/1"
ensure_write_succ "$filters_dir/nr_filters" "0" "valid input"
ensure_dir "$filters_dir/0" "not_exist"
ensure_dir "$filters_dir/1" "not_exist"
}
test_watermarks()
{
watermarks_dir=$1
ensure_dir "$watermarks_dir" "exist"
ensure_file "$watermarks_dir/metric" "exist" "600"
ensure_file "$watermarks_dir/interval_us" "exist" "600"
ensure_file "$watermarks_dir/high" "exist" "600"
ensure_file "$watermarks_dir/mid" "exist" "600"
ensure_file "$watermarks_dir/low" "exist" "600"
}
test_weights()
{
weights_dir=$1
ensure_dir "$weights_dir" "exist"
ensure_file "$weights_dir/sz_permil" "exist" "600"
ensure_file "$weights_dir/nr_accesses_permil" "exist" "600"
ensure_file "$weights_dir/age_permil" "exist" "600"
}
test_goal()
{
goal_dir=$1
ensure_dir "$goal_dir" "exist"
ensure_file "$goal_dir/target_value" "exist" "600"
ensure_file "$goal_dir/current_value" "exist" "600"
}
test_goals()
{
goals_dir=$1
ensure_dir "$goals_dir" "exist"
ensure_file "$goals_dir/nr_goals" "exist" "600"
ensure_write_succ "$goals_dir/nr_goals" "1" "valid input"
test_goal "$goals_dir/0"
ensure_write_succ "$goals_dir/nr_goals" "2" "valid input"
test_goal "$goals_dir/0"
test_goal "$goals_dir/1"
ensure_write_succ "$goals_dir/nr_goals" "0" "valid input"
ensure_dir "$goals_dir/0" "not_exist"
ensure_dir "$goals_dir/1" "not_exist"
}
test_quotas()
{
quotas_dir=$1
ensure_dir "$quotas_dir" "exist"
ensure_file "$quotas_dir/ms" "exist" 600
ensure_file "$quotas_dir/bytes" "exist" 600
ensure_file "$quotas_dir/reset_interval_ms" "exist" 600
test_weights "$quotas_dir/weights"
test_goals "$quotas_dir/goals"
}
test_access_pattern()
{
access_pattern_dir=$1
ensure_dir "$access_pattern_dir" "exist"
test_range "$access_pattern_dir/age"
test_range "$access_pattern_dir/nr_accesses"
test_range "$access_pattern_dir/sz"
}
test_scheme()
{
scheme_dir=$1
ensure_dir "$scheme_dir" "exist"
ensure_file "$scheme_dir/action" "exist" "600"
test_access_pattern "$scheme_dir/access_pattern"
ensure_file "$scheme_dir/apply_interval_us" "exist" "600"
test_quotas "$scheme_dir/quotas"
test_watermarks "$scheme_dir/watermarks"
test_filters "$scheme_dir/filters"
test_stats "$scheme_dir/stats"
test_tried_regions "$scheme_dir/tried_regions"
}
test_schemes()
{
schemes_dir=$1
ensure_dir "$schemes_dir" "exist"
ensure_file "$schemes_dir/nr_schemes" "exist" 600
ensure_write_succ "$schemes_dir/nr_schemes" "1" "valid input"
test_scheme "$schemes_dir/0"
ensure_write_succ "$schemes_dir/nr_schemes" "2" "valid input"
test_scheme "$schemes_dir/0"
test_scheme "$schemes_dir/1"
ensure_write_succ "$schemes_dir/nr_schemes" "0" "valid input"
ensure_dir "$schemes_dir/0" "not_exist"
ensure_dir "$schemes_dir/1" "not_exist"
}
test_region()
{
region_dir=$1
ensure_dir "$region_dir" "exist"
ensure_file "$region_dir/start" "exist" 600
ensure_file "$region_dir/end" "exist" 600
}
test_regions()
{
regions_dir=$1
ensure_dir "$regions_dir" "exist"
ensure_file "$regions_dir/nr_regions" "exist" 600
ensure_write_succ "$regions_dir/nr_regions" "1" "valid input"
test_region "$regions_dir/0"
ensure_write_succ "$regions_dir/nr_regions" "2" "valid input"
test_region "$regions_dir/0"
test_region "$regions_dir/1"
ensure_write_succ "$regions_dir/nr_regions" "0" "valid input"
ensure_dir "$regions_dir/0" "not_exist"
ensure_dir "$regions_dir/1" "not_exist"
}
test_target()
{
target_dir=$1
ensure_dir "$target_dir" "exist"
ensure_file "$target_dir/pid_target" "exist" "600"
test_regions "$target_dir/regions"
}
test_targets()
{
targets_dir=$1
ensure_dir "$targets_dir" "exist"
ensure_file "$targets_dir/nr_targets" "exist" 600
ensure_write_succ "$targets_dir/nr_targets" "1" "valid input"
test_target "$targets_dir/0"
ensure_write_succ "$targets_dir/nr_targets" "2" "valid input"
test_target "$targets_dir/0"
test_target "$targets_dir/1"
ensure_write_succ "$targets_dir/nr_targets" "0" "valid input"
ensure_dir "$targets_dir/0" "not_exist"
ensure_dir "$targets_dir/1" "not_exist"
}
test_intervals_goal()
{
goal_dir=$1
ensure_dir "$goal_dir" "exist"
ensure_file "$goal_dir/access_bp" "exist" "600"
ensure_file "$goal_dir/aggrs" "exist" "600"
ensure_file "$goal_dir/min_sample_us" "exist" "600"
ensure_file "$goal_dir/max_sample_us" "exist" "600"
}
test_intervals()
{
intervals_dir=$1
ensure_dir "$intervals_dir" "exist"
ensure_file "$intervals_dir/aggr_us" "exist" "600"
ensure_file "$intervals_dir/sample_us" "exist" "600"
ensure_file "$intervals_dir/update_us" "exist" "600"
test_intervals_goal "$intervals_dir/intervals_goal"
}
test_damon_filter()
{
damon_filter_dir=$1
ensure_file "$damon_filter_dir/type" "exist" "600"
ensure_write_succ "$damon_filter_dir/type" "anon" "valid input"
ensure_write_fail "$damon_filter_dir/type" "foo" "invalid input"
ensure_file "$damon_filter_dir/matching" "exist" "600"
ensure_file "$damon_filter_dir/allow" "exist" "600"
}
test_damon_filters()
{
filters_dir=$1
ensure_dir "$filters_dir" "exist"
ensure_file "$filters_dir/nr_filters" "exist" "600"
ensure_write_succ "$filters_dir/nr_filters" "1" "valid input"
test_damon_filter "$filters_dir/0"
ensure_write_succ "$filters_dir/nr_filters" "2" "valid input"
test_damon_filter "$filters_dir/0"
test_damon_filter "$filters_dir/1"
ensure_write_succ "$filters_dir/nr_filters" "0" "valid input"
ensure_dir "$filters_dir/0" "not_exist"
ensure_dir "$filters_dir/1" "not_exist"
}
test_probe()
{
probe_dir=$1
ensure_dir "$probe_dir" "exist"
test_damon_filters "$probe_dir/filters"
}
test_probes()
{
probes_dir=$1
ensure_dir "$probes_dir" "exist"
ensure_file "$probes_dir/nr_probes" "exist" "600"
ensure_write_succ "$probes_dir/nr_probes" "1" "valid input"
test_probe "$probes_dir/0"
ensure_write_succ "$probes_dir/nr_probes" "0" "valid input"
ensure_dir "$probes_dir/0" "not_exist"
}
test_monitoring_attrs()
{
monitoring_attrs_dir=$1
ensure_dir "$monitoring_attrs_dir" "exist"
test_intervals "$monitoring_attrs_dir/intervals"
test_probes "$monitoring_attrs_dir/probes"
test_range "$monitoring_attrs_dir/nr_regions"
}
test_context()
{
context_dir=$1
ensure_dir "$context_dir" "exist"
ensure_file "$context_dir/avail_operations" "exit" 400
ensure_file "$context_dir/operations" "exist" 600
ensure_file "$context_dir/addr_unit" "exist" 600
ensure_file "$context_dir/pause" "exist" 600
test_monitoring_attrs "$context_dir/monitoring_attrs"
test_targets "$context_dir/targets"
test_schemes "$context_dir/schemes"
}
test_contexts()
{
contexts_dir=$1
ensure_dir "$contexts_dir" "exist"
ensure_file "$contexts_dir/nr_contexts" "exist" 600
ensure_write_succ "$contexts_dir/nr_contexts" "1" "valid input"
test_context "$contexts_dir/0"
ensure_write_fail "$contexts_dir/nr_contexts" "2" "only 0/1 are supported"
test_context "$contexts_dir/0"
ensure_write_succ "$contexts_dir/nr_contexts" "0" "valid input"
ensure_dir "$contexts_dir/0" "not_exist"
}
test_kdamond()
{
kdamond_dir=$1
ensure_dir "$kdamond_dir" "exist"
ensure_file "$kdamond_dir/state" "exist" "600"
ensure_file "$kdamond_dir/pid" "exist" 400
test_contexts "$kdamond_dir/contexts"
}
test_kdamonds()
{
kdamonds_dir=$1
ensure_dir "$kdamonds_dir" "exist"
ensure_file "$kdamonds_dir/nr_kdamonds" "exist" "600"
ensure_write_succ "$kdamonds_dir/nr_kdamonds" "1" "valid input"
test_kdamond "$kdamonds_dir/0"
ensure_write_succ "$kdamonds_dir/nr_kdamonds" "2" "valid input"
test_kdamond "$kdamonds_dir/0"
test_kdamond "$kdamonds_dir/1"
ensure_write_succ "$kdamonds_dir/nr_kdamonds" "0" "valid input"
ensure_dir "$kdamonds_dir/0" "not_exist"
ensure_dir "$kdamonds_dir/1" "not_exist"
}
test_damon_sysfs()
{
damon_sysfs=$1
if [ ! -d "$damon_sysfs" ]
then
echo "$damon_sysfs not found"
exit $ksft_skip
fi
test_kdamonds "$damon_sysfs/kdamonds"
}
check_dependencies
test_damon_sysfs "/sys/kernel/mm/damon/admin"
@@ -0,0 +1,45 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
if [ $EUID -ne 0 ]
then
echo "Run as root"
exit $ksft_skip
fi
damon_sysfs="/sys/kernel/mm/damon/admin"
if [ ! -d "$damon_sysfs" ]
then
echo "damon sysfs not found"
exit $ksft_skip
fi
kmemleak="/sys/kernel/debug/kmemleak"
if [ ! -f "$kmemleak" ]
then
echo "$kmemleak not found"
exit $ksft_skip
fi
# ensure filter directory
echo 1 > "$damon_sysfs/kdamonds/nr_kdamonds"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/nr_contexts"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/0/schemes/nr_schemes"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/0/schemes/0/filters/nr_filters"
filter_dir="$damon_sysfs/kdamonds/0/contexts/0/schemes/0/filters/0"
# try to leak 128 times
for i in {1..128};
do
echo "012345678901234567890123456789" > "$filter_dir/memcg_path"
done
echo scan > "$kmemleak"
kmemleak_report=$(cat "$kmemleak")
if [ "$kmemleak_report" = "" ]
then
exit 0
fi
echo "$kmemleak_report"
exit 1
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import json
import os
import subprocess
import sys
import _damon_sysfs
def dump_damon_status_dict(pid):
try:
subprocess.check_output(['which', 'drgn'], stderr=subprocess.DEVNULL)
except:
return None, 'drgn not found'
file_dir = os.path.dirname(os.path.abspath(__file__))
dump_script = os.path.join(file_dir, 'drgn_dump_damon_status.py')
rc = subprocess.call(['drgn', dump_script, pid, 'damon_dump_output'],
stderr=subprocess.DEVNULL)
if rc != 0:
return None, f'drgn fail: return code({rc})'
try:
with open('damon_dump_output', 'r') as f:
return json.load(f), None
except Exception as e:
return None, 'json.load fail (%s)' % e
def main():
kdamonds = _damon_sysfs.Kdamonds(
[_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
schemes=[_damon_sysfs.Damos(
ops_filters=[
_damon_sysfs.DamosFilter(
type_='anon',
matching=True,
allow=True,
)
]
)],
)])]
)
err = kdamonds.start()
if err is not None:
print('kdamond start failed: %s' % err)
exit(1)
before_commit_status, err = \
dump_damon_status_dict(kdamonds.kdamonds[0].pid)
if err is not None:
print('before-commit status dump failed: %s' % err)
exit(1)
kdamonds.kdamonds[0].commit()
after_commit_status, err = \
dump_damon_status_dict(kdamonds.kdamonds[0].pid)
if err is not None:
print('after-commit status dump failed: %s' % err)
exit(1)
if before_commit_status != after_commit_status:
print(f'before: {json.dumps(before_commit_status, indent=2)}')
print(f'after: {json.dumps(after_commit_status, indent=2)}')
exit(1)
kdamonds.stop()
if __name__ == '__main__':
main()
@@ -0,0 +1,56 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source _common.sh
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
check_dependencies
damon_sysfs="/sys/kernel/mm/damon/admin"
if [ ! -d "$damon_sysfs" ]
then
echo "damon sysfs not found"
exit $ksft_skip
fi
# clear log
dmesg -C
# start DAMON with a scheme
echo 1 > "$damon_sysfs/kdamonds/nr_kdamonds"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/nr_contexts"
echo "vaddr" > "$damon_sysfs/kdamonds/0/contexts/0/operations"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/0/targets/nr_targets"
echo $$ > "$damon_sysfs/kdamonds/0/contexts/0/targets/0/pid_target"
echo 1 > "$damon_sysfs/kdamonds/0/contexts/0/schemes/nr_schemes"
scheme_dir="$damon_sysfs/kdamonds/0/contexts/0/schemes/0"
echo 4096000 > "$scheme_dir/access_pattern/sz/max"
echo 20 > "$scheme_dir/access_pattern/nr_accesses/max"
echo 1024 > "$scheme_dir/access_pattern/age/max"
echo "on" > "$damon_sysfs/kdamonds/0/state"
sleep 0.3
# remove scheme sysfs dir
echo 0 > "$damon_sysfs/kdamonds/0/contexts/0/schemes/nr_schemes"
# try to update stat of already removed scheme sysfs dir
echo "update_schemes_stats" > "$damon_sysfs/kdamonds/0/state"
if dmesg | grep -q BUG
then
echo "update_schemes_stats triggers a kernel bug"
dmesg
exit 1
fi
# try to update tried regions of already removed scheme sysfs dir
echo "update_schemes_tried_regions" > "$damon_sysfs/kdamonds/0/state"
if dmesg | grep -q BUG
then
echo "update_schemes_tried_regions triggers a kernel bug"
dmesg
exit 1
fi
echo "off" > "$damon_sysfs/kdamonds/0/state"
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def main():
proc = subprocess.Popen(['sleep', '2'])
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(
access_pattern=_damon_sysfs.DamosAccessPattern(
nr_accesses=[200, 200]))] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err != None:
print('kdamond start failed: %s' % err)
exit(1)
while proc.poll() == None:
err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
if err != None:
print('tried bytes update failed: %s' % err)
exit(1)
if __name__ == '__main__':
main()
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import subprocess
import time
import _damon_sysfs
def pass_wss_estimation(sz_region):
# access two regions of given size, 2 seocnds per each region
proc = subprocess.Popen(
['./access_memory', '2', '%d' % sz_region, '2000', 'repeat'])
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
contexts=[_damon_sysfs.DamonCtx(
ops='vaddr',
targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
schemes=[_damon_sysfs.Damos(
access_pattern=_damon_sysfs.DamosAccessPattern(
# >= 25% access rate, >= 200ms age
nr_accesses=[5, 20], age=[2, 2**64 - 1]))] # schemes
)] # contexts
)]) # kdamonds
err = kdamonds.start()
if err != None:
print('kdamond start failed: %s' % err)
exit(1)
wss_collected = []
while proc.poll() is None and len(wss_collected) < 40:
time.sleep(0.1)
err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
if err != None:
print('tried bytes update failed: %s' % err)
exit(1)
wss_collected.append(
kdamonds.kdamonds[0].contexts[0].schemes[0].tried_bytes)
proc.terminate()
err = kdamonds.stop()
if err is not None:
print('kdamond stop failed: %s' % err)
exit(1)
wss_collected.sort()
acceptable_error_rate = 0.2
for percentile in [50, 75]:
sample = wss_collected[int(len(wss_collected) * percentile / 100)]
error_rate = abs(sample - sz_region) / sz_region
print('%d-th percentile error %f (expect %d, result %d)' %
(percentile, error_rate, sz_region, sample))
if error_rate > acceptable_error_rate:
print('the error rate is not acceptable (> %f)' %
acceptable_error_rate)
print('samples are as below')
for idx, wss in enumerate(wss_collected):
if idx < len(wss_collected) - 1 and \
wss_collected[idx + 1] == wss:
continue
print('%d/%d: %d' % (idx, len(wss_collected), wss))
return False
return True
def main():
# DAMON doesn't flush TLB. If the system has large TLB that can cover
# whole test working set, DAMON cannot see the access. Test up to 160 MiB
# test working set.
sz_region_mb = 10
max_sz_region_mb = 160
while sz_region_mb <= max_sz_region_mb:
test_pass = pass_wss_estimation(sz_region_mb * 1024 * 1024)
if test_pass is True:
exit(0)
sz_region_mb *= 2
exit(1)
if __name__ == '__main__':
main()