blob: 46800764932c48969ab68e783e2364a7889020e2 [file] [log] [blame]
# SPDX-License-Identifier: GPL-2.0
"""
Command line arguments handling
"""
import argparse
import json
import os
import subprocess
# for non-python-default modules
try:
import yaml
except ModuleNotFoundError as e:
# do nothing. The yaml using functions should handle the exception
# properly.
pass
import _damo_subproc
import _damo_sysinfo
import _damon
import damo_pa_layout
# Kdamonds construction from command line arguments
def range_overlap_or_contig(range1, range2):
s1, e1 = range1
s2, e2 = range2
if e1 == s2 or e2 == s1:
return True
if e1 <= s2:
return False
if e2 <= s1:
return False
return True
def merge_ranges(ranges):
ranges.sort(key=lambda r: r[0])
merged_ranges = []
for start, end in ranges:
if len(merged_ranges) > 0 and range_overlap_or_contig(
merged_ranges[-1], [start, end]):
merged_ranges[-1] = [min(merged_ranges[-1][0], start),
max(merged_ranges[-1][1], end)]
else:
merged_ranges.append([start, end])
return merged_ranges
def init_regions_for(args_regions, args_ops, args_numa_node):
init_regions = []
if args_regions:
for region in args_regions.split():
addrs = region.split('-')
try:
if len(addrs) != 2:
raise Exception ('two addresses not given')
region = _damon.DamonRegion(addrs[0], addrs[1])
if region.start >= region.end:
raise Exception('start >= end')
if init_regions and init_regions[-1].end > region.start:
raise Exception('regions overlap')
except Exception as e:
return None, 'Wrong \'--regions\' argument (%s)' % e
init_regions.append(region)
if args_ops == 'paddr' and not init_regions:
if args_numa_node != None:
init_regions, err = damo_pa_layout.numa_addr_ranges(
args_numa_node)
if err != None:
return None, err
init_regions = merge_ranges(init_regions)
else:
init_regions = [damo_pa_layout.default_paddr_region()]
try:
init_regions = [_damon.DamonRegion(r[0], r[1])
for r in init_regions]
except Exception as e:
return None, 'Wrong \'--regions\' argument (%s)' % e
return init_regions, None
def override_vals(to_override, new_vals):
if new_vals is None:
return
for idx, new_val in enumerate(new_vals):
if new_val is not None:
to_override[idx] = new_val
def damon_intervals_for(args_intervals, args_sample, args_aggr, args_updr,
args_intervals_goal):
intervals = ['5ms', '100ms', '1s']
override_vals(intervals, args_intervals)
override_vals(intervals, [args_sample, args_aggr, args_updr])
if args_intervals_goal is None:
args_intervals_goal = ['0%', '0', '0us', '0us']
intervals_goal = _damon.DamonIntervalsGoal(*args_intervals_goal)
return _damon.DamonIntervals(*intervals, intervals_goal)
def damon_nr_regions_range_for(args_range, args_minr, args_maxr):
nr_range = ['10', '1000']
override_vals(nr_range, args_range)
override_vals(nr_range, [args_minr, args_maxr])
return _damon.DamonNrRegionsRange(*nr_range)
def schemes_option_to_damos(schemes):
if os.path.isfile(schemes):
with open(schemes, 'r') as f:
schemes = f.read()
try:
kvpairs = json.loads(schemes)
return [_damon.Damos.from_kvpairs(kv) for kv in kvpairs], None
except Exception as json_err:
return None, '%s' % json_err
def damos_filter_with_optional_args(ftype, fmatching, allow, optional_words):
# return filter, error, and number of consumed words
if ftype == 'memcg':
if len(optional_words) < 1:
return None, 'no memcg path is given', 0
memcg_path = optional_words[0]
return _damon.DamosFilter(ftype, fmatching, memcg_path=memcg_path,
allow=allow), None, 1
if ftype == 'addr':
if len(optional_words) < 2:
return None, 'no address range is given', 0
try:
addr_range = _damon.DamonRegion(optional_words[0], optional_words[1])
except Exception as e:
return None, 'wrong addr range (%s, %s)' % (optional_words, e), 0
return _damon.DamosFilter(ftype, fmatching, allow=allow,
address_range=addr_range), None, 2
elif ftype == 'target':
if len(optional_words) < 1:
return None, 'no target argument', 0
try:
return _damon.DamosFilter(
ftype, fmatching, allow=allow,
damon_target_idx=optional_words[0]), None, 1
except Exception as e:
return None, 'target filter creation failed (%s, %s)' % (
optional_words[0], e), 0
elif ftype == 'hugepage_size':
if len(optional_words) < 2:
return None, 'no range for hugepage sizes is given', 0
hugepage_size = [optional_words[0], optional_words[1]]
return _damon.DamosFilter(ftype, fmatching, allow=allow,
hugepage_size=hugepage_size), None, 2
return None, 'unsupported filter type', 0
def damos_options_to_filter_v2(words):
'''
Returns filter, error, and consumed number of words
'''
if len(words) < 2:
return None, 'wrong number of words: %s' % words, 0
if not words[0] in ['allow', 'reject', 'deny', 'block']:
return None, 'unsupported allow-reject idntifier: %s' % words[0], 0
allow = words[0] == 'allow'
nr_consumed_words = 1
word = words[nr_consumed_words]
if word == 'none':
fmatching = False
nr_consumed_words += 1
else:
fmatching = True
ftype = words[nr_consumed_words]
nr_consumed_words += 1
if ftype in ['anon', 'active', 'young', 'unmapped']:
filter = _damon.DamosFilter(ftype, fmatching, allow=allow)
return filter, None, nr_consumed_words
else:
filter, err, nr_words = damos_filter_with_optional_args(
ftype, fmatching, allow, words[nr_consumed_words:])
if err is not None:
return None, 'handling %s fail (%s)' % (words, err), 0
return filter, None, nr_consumed_words + nr_words
def damos_options_to_filters_v2(words):
filters = []
while len(words) > 0:
filter, err, nr_consumed_words = damos_options_to_filter_v2(words)
if err is not None:
return None, err
filters.append(filter)
words = words[nr_consumed_words:]
return filters, None
def convert_damos_filter_v1_to_v2(filter_args):
if len(filter_args) < 2:
return None, '<2 filter argument'
filter_type = filter_args[0]
matching = filter_args[1] == 'matching'
optional_args = filter_args[2:]
if len(optional_args) == 0:
allow_reject = 'reject'
else:
if optional_args[-1] in ['allow', 'pass', 'reject', 'deny', 'block']:
if optional_args[-1] in ['allow', 'pass']:
allow_reject = 'allow'
else:
allow_reject = 'reject'
optional_args = optional_args[:-1]
else:
allow_reject = 'reject'
v2_args = [allow_reject]
if matching is False:
v2_args.append('none')
return v2_args + [filter_type] + optional_args, None
def damos_options_to_filters(filters_args):
filters = []
if filters_args == None:
return filters, None
full_words = []
for fields in filters_args:
full_words += fields
filters, v2_err = damos_options_to_filters_v2(full_words)
if v2_err is None:
return filters, None
full_words = []
for filter_args in filters_args:
converted_filter_args, err = convert_damos_filter_v1_to_v2(filter_args)
if err is not None:
return None, 'converting format fail (%s, %s)' % (filter_args, err)
full_words += converted_filter_args
filters, v2_err = damos_options_to_filters_v2(full_words)
return filters, v2_err
def damos_quotas_cons_arg(cmd_args):
time_ms = 0
sz_bytes = 0
reset_interval_ms = 'max'
weights = ['0 %', '0 %', '0 %']
nr_cmd_args = len(cmd_args)
if nr_cmd_args >= 1:
time_ms = cmd_args[0]
if nr_cmd_args >= 2:
sz_bytes = cmd_args[1]
if nr_cmd_args >= 3:
reset_interval_ms = cmd_args[2]
if nr_cmd_args >= 4:
weights[0] = cmd_args[3]
if nr_cmd_args >= 5:
weights[1] = cmd_args[4]
if nr_cmd_args >= 6:
weights[2] = cmd_args[5]
return [time_ms, sz_bytes, reset_interval_ms, weights]
def damos_options_to_quota_goal(garg):
# garg is the user inputs
# garg should be <metric> <target value> [<optional>...]
# for user_input, one optional argument for "current value" is given.
# for node_mem[cg]_{used,free}_bp, one optional argument for node id is
# given.
# for node_memcg_{used,free}_bp, one more optional argument for memcg path
# is given.
if not len(garg) in [2, 3, 4]:
return None, 'Wrong --damos_quota_goal (%s)' % garg
metric, target_value, optionals = garg[0], garg[1], garg[2:]
current_value = 0
nid = None
memcg_path = None
if metric in [_damon.qgoal_node_mem_used_bp,
_damon.qgoal_node_mem_free_bp]:
if len(optionals) != 1:
return None, 'nid is not given or something else is given'
nid = optionals[0]
elif metric in [_damon.qgoal_node_memcg_used_bp,
_damon.qgoal_node_memcg_free_bp]:
if len(optionals) != 2:
return None, 'nid and memcg_path required'
nid = optionals[0]
memcg_path = optionals[1]
elif metric == 'user_input':
if len(optionals) != 1:
return None, 'current value is not given or something else is given'
current_value = optionals[0]
try:
return _damon.DamosQuotaGoal(
metric=metric, target_value=target_value,
current_value=current_value,
nid=nid, memcg_path=memcg_path), None
except Exception as e:
return None, 'DamosQuotaGoal creation fail (%s, %s)' % (garg, e)
def damos_options_to_quotas(quotas, goals):
gargs = goals
goals = []
for garg in gargs:
goal, err = damos_options_to_quota_goal(garg)
if err is not None:
return None, 'quota goals parsing fail (%s)' % err
goals.append(goal)
qargs = quotas
if len(qargs) > 6:
return None, 'Wrong --damos_quotas (%s, >6 parameters)' % qargs
try:
quotas = _damon.DamosQuotas(*damos_quotas_cons_arg(qargs),
goals=goals)
except Exception as e:
return None, 'Wrong --damos_quotas (%s, %s)' % (qargs, e)
return quotas, None
def damos_options_to_scheme(
sz_region, access_rate, age, action,
apply_interval, quotas, goals, wmarks, target_nid, filters, dests,
max_nr_snapshots):
if quotas != None:
quotas, err = damos_options_to_quotas(quotas, goals)
if err is not None:
return None, err
if wmarks != None:
wargs = wmarks
try:
wmarks = _damon.DamosWatermarks(wargs[0], wargs[1], wargs[2],
wargs[3], wargs[4])
except Exception as e:
return None, 'Wrong --damos_wmarks (%s, %s)' % (wargs, e)
filters, err = damos_options_to_filters(filters)
if err != None:
return None, err
if max_nr_snapshots is None:
stats = _damon.DamosStats()
else:
stats = _damon.DamosStats(max_nr_snapshots=max_nr_snapshots)
try:
return _damon.Damos(
access_pattern=_damon.DamosAccessPattern(sz_region,
access_rate, _damon.unit_percent, age, _damon.unit_usec),
action=action, target_nid=target_nid,
dests=dests, apply_interval_us=apply_interval, quotas=quotas,
watermarks=wmarks, filters=filters, stats=stats), None
except Exception as e:
return None, 'Wrong \'--damos_*\' argument (%s)' % e
def set_args_damos_quotas(args):
# DAMOS quotas can be set using
# 1) only --damos_quotas (specify time, space, interval, weights at once),
# or
# 2) --damos_quota_interval, --damos_quota_time, --damos_quota_space,
# and --damos_quota_weights (specify the components one by one).
# Convert quotas specified in 2) way into --damos_quotas for easier
# handling.
if not args.damos_quota_interval:
return
for i, interval in enumerate(args.damos_quota_interval):
t, s = 0, 0
if i < len(args.damos_quota_time):
t = args.damos_quota_time[i]
if i < len(args.damos_quota_space):
s = args.damos_quota_space[i]
if i < len(args.damos_quota_weights):
w1, w2, w3 = args.damos_quota_weights[i]
else:
w1, w2, w3 = 1, 1, 1
args.damos_quotas.append([t, s, interval, w1, w2, w3])
def verify_set_damos_args_len(args):
# return an error string if given args is wrong in terms of lengths.
nr_schemes = len(args.damos_action)
if len(args.damos_sz_region) > nr_schemes:
return 'too much --damos_sz_region'
if len(args.damos_access_rate) > nr_schemes:
return 'too much --damos_access_rate'
if len(args.damos_age) > nr_schemes:
return 'too much --damos_age'
if len(args.damos_apply_interval) > nr_schemes:
return 'too much --damos_apply_interval'
if len(args.damos_quotas) > nr_schemes:
return 'too much --damos_quotas'
if len(args.damos_quota_goal) > 0 and nr_schemes > 1:
if len(args.damos_nr_quota_goals) == 0:
return '--damos_nr_quota_goals required'
if nr_schemes == 1 and args.damos_nr_quota_goals == []:
args.damos_nr_quota_goals = [len(args.damos_quota_goal)]
if sum(args.damos_nr_quota_goals) != len(args.damos_quota_goal):
return 'wrong --damos_nr_quota_goals'
if len(args.damos_wmarks) > nr_schemes:
return 'too much --damos_wmarks'
# for multiple schemes, number of filters per scheme is required
if len(args.damos_filter) > 0 and nr_schemes > 1:
if len(args.damos_nr_filters) == 0:
return '--damos_nr_filters required'
if nr_schemes == 1 and args.damos_nr_filters == []:
args.damos_nr_filters = [len(args.damos_filter)]
if sum(args.damos_nr_filters) != len(args.damos_filter):
return 'wrong --damos_nr_filters'
if len(args.damos_max_nr_snapshots) > nr_schemes:
return 'too much --damos_max_nr_snapshots'
return None
def fillup_default_damos_args(args):
nr_schemes = len(args.damos_action)
args.damos_sz_region += [['min', 'max']] * (
nr_schemes - len(args.damos_sz_region))
args.damos_access_rate += [['min', 'max']] * (
nr_schemes - len(args.damos_access_rate))
args.damos_age += [['min', 'max']] * (nr_schemes - len(args.damos_age))
args.damos_apply_interval += [0] * (
nr_schemes - len(args.damos_apply_interval))
args.damos_quotas += [None] * (nr_schemes - len(args.damos_quotas))
args.damos_wmarks += [None] * (nr_schemes - len(args.damos_wmarks))
args.damos_max_nr_snapshots += [None] * (
nr_schemes - len(args.damos_max_nr_snapshots))
def damos_options_to_schemes(args):
set_args_damos_quotas(args)
err = verify_set_damos_args_len(args)
if err is not None:
return [], err
fillup_default_damos_args(args)
nr_schemes = len(args.damos_action)
schemes = []
for i in range(nr_schemes):
action = args.damos_action[i][0]
target_nid = None
dests = []
if _damon.is_damos_migrate_action(action):
try:
if len(args.damos_action[i]) == 2:
target_nid = int(args.damos_action[i][1])
elif len(args.damos_action[i]) > 2:
dests = []
for j in range(1, len(args.damos_action[i]), 2):
dests.append(_damon.DamosDest(
args.damos_action[i][j],
args.damos_action[i][j + 1]))
except:
return [], '"%s" action require a numeric target_nid ' \
'or dests arguments.' \
% args.damos_action[i][0]
elif len(args.damos_action[i]) > 1:
return [], 'Wrong number of --damos_action arguments.' % action
args.damos_action[i] = action
qgoals = []
if args.damos_quota_goal:
goal_start_index = sum(args.damos_nr_quota_goals[:i])
goal_end_index = goal_start_index + args.damos_nr_quota_goals[i]
qgoals = args.damos_quota_goal[goal_start_index:goal_end_index]
filters = []
if args.damos_filter:
filter_start_index = sum(args.damos_nr_filters[:i])
filter_end_index = filter_start_index + args.damos_nr_filters[i]
filters = args.damos_filter[filter_start_index:filter_end_index]
scheme, err = damos_options_to_scheme(
args.damos_sz_region[i],
args.damos_access_rate[i], args.damos_age[i],
args.damos_action[i], args.damos_apply_interval[i],
args.damos_quotas[i], qgoals, args.damos_wmarks[i],
target_nid, filters, dests, args.damos_max_nr_snapshots[i])
if err != None:
return [], err
schemes.append(scheme)
return schemes, None
def damos_for(args):
if args.damos_action:
schemes, err = damos_options_to_schemes(args)
if err != None:
return None, err
return schemes, None
if not 'schemes' in args or args.schemes == None:
return [], None
schemes, err = schemes_option_to_damos(args.schemes)
if err:
return None, 'failed damo schemes arguments parsing (%s)' % err
return schemes, None
def damon_target_for(args, idx, ops):
init_regions, err = init_regions_for(
args.regions[idx], ops, args.numa_node[idx])
if err:
return None, err
try:
obsolete = False
if args.obsolete_targets is not None and idx in args.obsolete_targets:
obsolete = True
target = _damon.DamonTarget(
args.target_pid[idx] if _damon.target_has_pid(ops) else None,
init_regions, obsolete=obsolete)
except Exception as e:
return None, 'Wrong \'--target_pid\' argument (%s)' % e
return target, None
def sample_control_to_ops_attrs_args(args, idx):
sample_primitives = args.sample_primitives[idx]
if sample_primitives is None:
return None
ops_use_reports = args.exp_ops_use_reports[idx]
if sample_primitives is not None and ops_use_reports is not None:
return '--sample_primitives and --exp_ops_use_reports used together'
if sample_primitives == ['page_table']:
args.exp_ops_use_reports[idx] = 'N'
elif sample_primitives == ['page_fault']:
args.exp_ops_use_reports[idx] = 'Y'
else:
return '--sample_primitives of %s is not supported for now' % \
sample_primitives
def build_sample_control_ops_attrs(args, idx):
'''
Returns DamonSampleControl, OpsAttrs, and an error
'''
err = sample_control_to_ops_attrs_args(args, idx)
if err is not None:
return None, None, err
if args.exp_ops_use_reports[idx] is not None:
use_reports = args.exp_ops_use_reports[idx]
else:
use_reports = False
if args.exp_ops_write_only[idx] is not None:
write_only = args.exp_ops_write_only[idx]
else:
write_only = False
if args.exp_ops_cpus[idx] is not None:
cpus = args.exp_ops_cpus[idx]
else:
cpus = 'all'
if args.exp_ops_tids[idx] is not None:
tids = args.exp_ops_tids[idx]
else:
tids = ''
use_sample_control = _damo_sysinfo.damon_feature_available(
'sysfs/damon_sample_control')
if use_sample_control is False:
try:
ops_attrs = _damon.OpsAttrs(
use_reports=use_reports, write_only=write_only, cpus=cpus,
tids=tids)
except Exception as e:
return None, None, 'invalid ops_attrs arguments (%s)' % e
return None, ops_attrs, None
if use_reports:
page_table, page_fault = False, True
else:
page_table, page_fault = True, False
primitives_enabled = _damon.DamonPrimitivesEnabled(
page_table=page_table, page_fault=page_fault)
sample_filters = []
if write_only:
sample_filters.append(_damon.DamonSampleFilter(
filter_type=_damon.damon_filter_type_write, matching=True,
allow=True))
if cpus != 'all':
sample_filters.append(_damon.DamonSampleFilter(
filter_type=_damon.damon_filter_type_cpumask, matching=True,
allow=True, cpumask=cpus))
if tids != '':
sample_filters.append(_damon.DamonSampleFilter(
filter_type=_damon.damon_filter_type_threads, matching=True,
allow=True, tid_arr=tids))
sample_control = _damon.DamonSampleControl(
primitives_enabled=primitives_enabled,
sample_filters=sample_filters)
return sample_control, None, None
def damon_ctx_for(args, idx):
if args.ops[idx] is None:
if args.target_pid[idx] is None:
args.ops[idx] = 'paddr'
else:
args.ops[idx] = 'vaddr'
try:
intervals = damon_intervals_for(
args.monitoring_intervals[idx], args.sample[idx],
args.aggr[idx], args.updr[idx],
args.monitoring_intervals_goal[idx])
except Exception as e:
return None, 'invalid intervals arguments (%s)' % e
try:
nr_regions = damon_nr_regions_range_for(
args.monitoring_nr_regions_range[idx],
args.minr[idx], args.maxr[idx])
except Exception as e:
return None, 'invalid nr_regions arguments (%s)' % e
ops = args.ops[idx]
sample_control, ops_attrs, err = build_sample_control_ops_attrs(args, idx)
if err is not None:
return None, 'exp_ops_* handling fail (%s)' % err
try:
ctx = _damon.DamonCtx(
ops, None, intervals, nr_regions, schemes=[],
sample_control=sample_control, ops_attrs=ops_attrs)
return ctx, None
except Exception as e:
return None, 'Creating context from arguments failed (%s)' % e
def get_nr_ctxs(args):
candidates = []
for v in [args.ops, args.sample, args.aggr, args.updr, args.minr,
args.maxr, args.monitoring_intervals,
args.monitoring_intervals_goal,
args.monitoring_nr_regions_range]:
if v is not None:
candidates.append(len(v))
if args.nr_targets is not None:
candidates.append(len(args.nr_targets))
if args.nr_schemes is not None:
candidates.append(len(args.nr_schemes))
if len(candidates) == 0:
return 1
return max(candidates)
def get_nr_targets(args):
candidates = []
for v in [args.target_pid, args.regions, args.numa_node]:
if v is not None:
candidates.append(len(v))
candidates.append(get_nr_ctxs(args))
return max(candidates)
def fillup_none_ctx_args(args):
nr_ctxs = get_nr_ctxs(args)
for attr_name in [
'ops', 'exp_ops_use_reports', 'exp_ops_write_only', 'exp_ops_cpus',
'exp_ops_tids', 'sample', 'aggr', 'updr', 'minr', 'maxr',
'monitoring_intervals', 'monitoring_intervals_goal',
'monitoring_nr_regions_range', 'sample_primitives']:
attr_val = getattr(args, attr_name)
if attr_val is None:
setattr(args, attr_name, [None] * nr_ctxs)
elif len(attr_val) < nr_ctxs:
setattr(args, attr_name,
attr_val + [None] * (nr_ctxs - len(attr_val)))
def fillup_none_target_args(args):
nr_targets = get_nr_targets(args)
for attr_name in ['target_pid', 'regions', 'numa_node']:
attr_val = getattr(args, attr_name)
if attr_val is None:
setattr(args, attr_name, [None] * nr_targets)
elif len(attr_val) < nr_targets:
print(attr_name, attr_val)
setattr(args, attr_name,
attr_val + [None] * (nr_targets - len(attr_val)))
def valid_obsolete_targets_args(obsolete_targets, nr_targets):
if obsolete_targets is None:
return True
for target_idx in obsolete_targets:
if target_idx >= nr_targets:
return False
return True
def gen_assign_targets(ctxs, args):
nr_targets = get_nr_targets(args)
if not valid_obsolete_targets_args(args.obsolete_targets, nr_targets):
return 'Invalid --obsolete_targets'
targets = []
if args.nr_targets is None:
if len(ctxs) != 1:
return '--nr_targets is required for multiple contexts'
args.nr_targets = [nr_targets]
for idx in range(nr_targets):
for i in range(len(args.nr_targets)):
if idx < sum(args.nr_targets[:i + 1]):
ops = args.ops[i]
break
target, err = damon_target_for(args, idx, ops)
if err is not None:
return err
targets.append(target)
if sum(args.nr_targets) != len(targets):
return '--nr_targets and number of targets mismatch (%d != %d)' % (
sum(args.nr_targets), len(targets))
if len(args.nr_targets) != len(ctxs):
return '--nr_targets and number of ctxs mismatch (%d != %d)' % (
len(args.nr_targets), len(ctxs))
ctx_idx = 0
target_idx = 0
for nr in args.nr_targets:
ctxs[ctx_idx].targets = targets[target_idx:target_idx + nr]
ctx_idx += 1
target_idx += nr
return None
def gen_assign_schemes(ctxs, args):
schemes, err = damos_for(args)
if err is not None:
return err
if args.nr_schemes is None:
if len(ctxs) != 1 and len(schemes) > 0:
return '--nr_schemes is required for multiple contexts'
args.nr_schemes = [len(schemes)]
args.nr_schemes += [0] * (len(ctxs) - 1)
if sum(args.nr_schemes) != len(schemes):
return '--nr_schemes and number of schemes mismatch (%d != %d)' % (
sum(args.nr_schemes), len(schemes))
if len(args.nr_schemes) != len(ctxs):
return '--nr_schemes and number of ctxs mismatch (%d != %d)' % (
len(args.nr_schemes), len(ctxs))
ctx_idx = 0
scheme_idx = 0
for nr in args.nr_schemes:
ctxs[ctx_idx].schemes = schemes[scheme_idx:scheme_idx + nr]
ctx_idx += 1
scheme_idx += nr
return None
def damon_ctxs_for(args):
fillup_none_ctx_args(args)
fillup_none_target_args(args)
ctxs = []
for idx in range(get_nr_ctxs(args)):
ctx, err = damon_ctx_for(args, idx)
if err is not None:
return None, err
ctxs.append(ctx)
err = gen_assign_targets(ctxs, args)
if err is not None:
return None, err
err = gen_assign_schemes(ctxs, args)
if err is not None:
return None, err
return ctxs, err
def kdamonds_from_json_arg(arg):
try:
if os.path.isfile(arg):
with open(arg, 'r') as f:
kdamonds_str = f.read()
else:
kdamonds_str = arg
kdamonds_kvpairs = json.loads(kdamonds_str)['kdamonds']
return [_damon.Kdamond.from_kvpairs(kvp)
for kvp in kdamonds_kvpairs], None
except Exception as e:
return None, e
def kdamonds_from_yaml_arg(arg):
try:
assert os.path.isfile(arg)
with open(arg, 'r') as f:
kdamonds_str = f.read()
kdamonds_kvpairs = yaml.safe_load(kdamonds_str)['kdamonds']
return [_damon.Kdamond.from_kvpairs(kvp)
for kvp in kdamonds_kvpairs], None
except Exception as e:
return None, e
target_type_explicit = 'explicit'
target_type_cmd = 'cmd'
target_type_pid = 'pid'
target_type_unknown = None
def deduced_target_type(target):
if target in ['vaddr', 'paddr', 'fvaddr']:
return target_type_explicit
if _damo_subproc.avail_cmd(target.split()[0]):
return target_type_cmd
else:
pass
try:
pid = int(target)
return target_type_pid
except:
pass
return target_type_unknown
def warn_option_override(option_name):
print('warning: %s is overridden by <deducible target>' % option_name)
def deduce_target_update_args(args):
'deducible target supports only single context/single kdamond'
args.self_started_target = False
target_type = deduced_target_type(args.deducible_target)
if target_type == target_type_unknown:
return 'target \'%s\' is not supported' % args.deducible_target
if target_type == target_type_explicit and args.deducible_target == 'paddr':
if not args.ops in [['paddr'], None]:
warn_option_override('--ops')
args.ops = ['paddr']
if args.target_pid != None:
warn_option_override('--target_pid')
args.target_pid = None
return None
if target_type == target_type_cmd:
p = subprocess.Popen(args.deducible_target, shell=True,
executable='/bin/bash')
pid = p.pid
args.self_started_target = True
elif target_type == target_type_pid:
pid = int(args.deducible_target)
if args.target_pid != None:
print('warning: --target_pid will be ignored')
args.target_pid = [pid]
if not args.regions:
if not args.ops in [['vaddr'], None]:
warn_option_override('--ops')
args.ops = ['vaddr']
if args.regions:
if not args.ops in [['fvaddr'], None]:
print('warning: override --ops by <deducible target> and --regions')
args.ops = ['fvaddr']
def evaluate_args(args):
'''
Verify if 'damons_action' is present when any 'damos_*' is specified
'''
if not args.damos_action:
for key, value in args.__dict__.items():
if key.startswith('damos_') and len(value):
if key == 'damos_action': continue
return False, '\'damos_action\' not specified while using --damos_* option(s)'
if len(args.damos_quotas) > 0 and len(args.damos_quota_interval) > 0:
return False, '--damos_quotas and --damos_quota_interval cannot passed together'
'''
Verify if 'reset_interval_ms' is specified in args when setting quota goals
'''
if args.damos_quota_goal:
damos_quotas = args.damos_quotas
if not len(damos_quotas) and not len(args.damos_quota_interval):
return False, '\'reset_interval_ms\' not specified when setting quota goals'
#reset_interval_ms is specified in --damos_quotas as 3rd arg
for quota in damos_quotas:
if len(quota) < 3:
return False, '\'reset_interval_ms\' not specified when setting quota goals'
return True, None
def kdamonds_for(args):
correct, err = evaluate_args(args)
if err is not None:
return None, err
if args.kdamonds:
return kdamonds_from_json_arg(args.kdamonds)
if args.deducible_target:
if args.deducible_target.endswith('.yaml'):
kdamonds, e = kdamonds_from_yaml_arg(args.deducible_target)
else:
kdamonds, e = kdamonds_from_json_arg(args.deducible_target)
if e == None:
return kdamonds, e
err = deduce_target_update_args(args)
if err:
return None, err
ctxs, err = damon_ctxs_for(args)
if err:
return None, err
if args.nr_ctxs is None:
args.nr_ctxs = [len(ctxs)]
if sum(args.nr_ctxs) != len(ctxs):
return None, '--nr_ctxs and number of ctxs mismatch (%d != %d)' % (
sum(args.nr_ctxs), len(ctxs))
kdamonds = []
ctx_idx = 0
for nr in args.nr_ctxs:
if (args.refresh_stat is not None and
len(args.refresh_stat) > len(kdamonds)):
refresh_ms = args.refresh_stat[len(kdamonds)]
else:
refresh_ms = 0
try:
kdamonds.append(_damon.Kdamond(
state=None, pid=None, refresh_ms=refresh_ms,
contexts=ctxs[ctx_idx:ctx_idx + nr]))
except Exception as e:
return None, 'kdamond creation fail (%s)' % e
ctx_idx += nr
return kdamonds, None
def self_started_target(args):
return 'self_started_target' in args and args.self_started_target
# Command line processing helpers
def is_ongoing_target(args):
return args.deducible_target == 'ongoing'
def stage_kdamonds(args):
kdamonds, err = kdamonds_for(args)
if err:
return None, 'cannot create kdamonds from args (%s)' % err
err = _damon.stage_kdamonds(kdamonds)
if err:
return None, 'cannot apply kdamonds from args (%s)' % err
return kdamonds, None
def damos_filter_invalidity(filter):
# todo: support non-memcg filters
if filter.filter_type != 'memcg':
return None
memcg_path = filter.memcg_path
if not memcg_path.startswith('/'):
return 'path should start with "/"'
cgroup_mount_path = None
with open('/proc/mounts', 'r') as f:
for line in f:
fields = line.split()
if not fields[2] in ['cgroup', 'cgroup2']:
continue
cgroup_mount_path = fields[1]
break
if cgroup_mount_path is None:
return 'cgroup is not mounted'
memcg_realpath = os.path.join(cgroup_mount_path, memcg_path[1:])
if not os.path.isdir(memcg_realpath):
return '%s not found' % memcg_realpath
return None
def infer_start_or_commit_fail_reason(kdamonds):
for kidx, kd in enumerate(kdamonds):
for cidx, ctx in enumerate(kd.contexts):
for sidx, scheme in enumerate(ctx.schemes):
for fidx, filter in enumerate(scheme.filters):
reason = damos_filter_invalidity(filter)
if reason is not None:
return ''.join([
'wrong %d/%d/%d/%d-th ' % (kidx, cidx, sidx, fidx),
'kdamond/context/scheme/filter (%s)' % reason])
return None
def commit_kdamonds(args, commit_quota_goals_only):
kdamonds, err = kdamonds_for(args)
if err:
return None, 'cannot create kdamonds to commit from args (%s)' % err
err = _damon.commit(kdamonds, commit_quota_goals_only)
if err:
inferred_reason = infer_start_or_commit_fail_reason(kdamonds)
if inferred_reason is not None:
err = '%s (%s)' % (err, inferred_reason)
return None, 'cannot commit kdamonds (%s)' % err
return kdamonds, None
def turn_damon_on(args):
kdamonds, err = stage_kdamonds(args)
if err:
return err, None
err = _damon.turn_damon_on(
['%s' % kidx for kidx, k in enumerate(kdamonds)])
if err is not None:
inferred_reason = infer_start_or_commit_fail_reason(kdamonds)
if inferred_reason is not None:
err = '%s (%s)' % (err, inferred_reason)
return err, None
return None, kdamonds
# Commandline options setup helpers
def set_common_argparser(parser):
parser.add_argument('--damon_interface_DEPRECATED',
choices=['sysfs', 'debugfs', 'auto'],
default='auto',
# underlying DAMON interface to use (!! DEPRECATED)
help=argparse.SUPPRESS)
parser.add_argument('--debug_damon', action='store_true',
help='Print debugging log')
def set_monitoring_attrs_pinpoint_argparser(parser, hide_help=False):
# for easier pinpoint setup
parser.add_argument(
'-s', '--sample', metavar='<microseconds>', action='append',
help='sampling interval (us)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'-a', '--aggr', metavar='<microseconds>', action='append',
help='aggregate interval (us)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'-u', '--updr', metavar='<microseconds>', action='append',
help='regions update interval (us)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'-n', '--minr', metavar='<# regions>', action='append',
help='minimal number of regions'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'-m', '--maxr', metavar='<# regions>', action='append',
help='maximum number of regions'
if not hide_help else argparse.SUPPRESS)
def set_monitoring_attrs_argparser(parser, hide_help=False):
# for easier total setup
parser.add_argument('--monitoring_intervals', nargs=3, action='append',
metavar=('<sample>', '<aggr>', '<update>'),
help='monitoring intervals (us)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--monitoring_intervals_goal', nargs=4, action='append',
metavar=('<access_bp>', '<aggrs>', '<min_sample_us>',
'<max_sample_us>'),
help='monitoring intervals auto-tuning goal'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--monitoring_nr_regions_range', nargs=2,
metavar=('<min>', '<max>'), action='append',
help='min/max number of monitoring regions'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--sample_primitives', action='append',
choices=['page_table', 'page_fault'], nargs='+',
help='access sampling primitives to use'
if not hide_help else argparse.SUPPRESS)
def set_monitoring_damos_common_args(parser, hide_help=False):
parser.add_argument('--ops', choices=['vaddr', 'paddr', 'fvaddr'],
action='append',
help='monitoring operations set'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--exp_ops_use_reports', action='append',
metavar='<Y|N>',
help='use access reports (experimental)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--exp_ops_write_only', action='append',
metavar='<Y|N>',
help='monitor writes only (experimental)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--exp_ops_cpus', action='append', metavar='<cpulist>',
help='monitor for given cpus only (experimental)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--exp_ops_tids', action='append', metavar='<thread ids list>',
help='monitoring for given threads only (experimental)')
parser.add_argument(
'--refresh_stat', metavar='<milliseconds>', action='append',
help='automatic kdamond internal stat refresh interval')
def set_monitoring_argparser(parser, hide_help=False):
parser.add_argument('--target_pid', type=int, metavar='<pid>',
action='append',
help='pid of monitoring target process'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('-r', '--regions', metavar='"<start>-<end> ..."',
type=str, action='append',
help='monitoring target address regions'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--numa_node', metavar='<node id>', type=int, nargs='+',
action='append',
help='if target is \'paddr\', limit it to the numa node'
if not hide_help else argparse.SUPPRESS)
set_monitoring_attrs_argparser(parser, hide_help)
parser.add_argument(
'--nr_targets', metavar='<number>', nargs='+', type=int,
help='number of monitoring targets for each context (in order)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--obsolete_targets', metavar='<target index>',
nargs='+', type=int,
help='obsolte targets'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--nr_ctxs', metavar='<number>', nargs='+', type=int,
help='number of contexts for each kdamond (in order)'
if not hide_help else argparse.SUPPRESS)
def set_damos_argparser(parser, hide_help):
parser.add_argument('--damos_action', metavar='<action>', nargs='+',
action='append', default=[],
help=' '.join([
'damos action to apply to the target regions.',
'<action> should be {%s}.' %
','.join(_damon.damos_actions),
'for \'migrate_{hot,cold}\' actions,',
'single target node id or',
'multiple destination nodes input',
'should also be given.'
])
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--damos_sz_region', metavar=('<min>', '<max>'),
nargs=2, default=[], action='append',
help='min/max size of damos target regions (bytes)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_access_rate', metavar=('<min>', '<max>'),
nargs=2, default=[], action='append',
help='min/max access rate of damos target regions (percent)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_age', metavar=('<min>', '<max>'), nargs=2,
default=[], action='append',
help='min/max age of damos target regions (microseconds)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--damos_apply_interval', metavar='<microseconds>',
action='append', default=[],
help='the apply interval for the scheme'
if not hide_help else argparse.SUPPRESS)
damos_quotas_help = ' '.join([
'damos quotas (<time (ms)> [<size (bytes)> [<reset interval (ms)>',
'[<size priority weight (permil)>',
'[<access rate priority weight> (permil)',
'[<age priority weight> (permil)]]]]])'])
parser.add_argument(
'--damos_quotas', default=[],
metavar='<quota parameter>', nargs='+', action='append',
help=argparse.SUPPRESS)
parser.add_argument('--damos_quota_interval', default=[],
metavar='<milliseconds>', action='append',
help='quota reset interval'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--damos_quota_time', default=[],
metavar='<milliseconds>', action='append',
help='time quota'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--damos_quota_space', default=[], metavar='<bytes>',
action='append', help='space quota'
if not hide_help else argparse.SUPPRESS)
parser.add_argument('--damos_quota_weights', default=[],
metavar='<permil>', nargs=3, action='append',
help='quota\'s prioritization weights'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_quota_goal', nargs='+', action='append',
default=[],
metavar='<metric or value>',
help=' '.join([
'damos quota goal (<metric> <target value> [optional value]).',
'<metric> should be {%s}.' %
','.join(_damon.qgoal_metrics)])
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_nr_quota_goals', type=int, nargs='+',
default=[], metavar='<integer>',
help='number of quota goals for each scheme (in order)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_filter', nargs='+', action='append',
default=[],
metavar='<<allow|reject> [none] <type> [option]...>',
help='damos filter' if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_nr_filters', type=int, nargs='+', default=[],
metavar='<integer>',
help='number of filters for each scheme (in order)'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_wmarks', nargs=5, action='append', default=[],
metavar=('<metric (none|free_mem_rate)>', '<interval (us)>',
'<high mark (permil)>', '<mid mark (permil)>',
'<low mark (permil)>'),
help='damos watermarks'
if not hide_help else argparse.SUPPRESS)
parser.add_argument(
'--damos_max_nr_snapshots', action='append', default=[],
help='damos max_nr_snapshots')
parser.add_argument('--nr_schemes', metavar='<number>', nargs='+', type=int,
help='number of schemes for each context (in order)'
if not hide_help else argparse.SUPPRESS)
def set_misc_damon_params_argparser(parser):
parser.add_argument('-c', '--schemes', metavar='<json string or file>',
help='data access monitoring-based operation schemes')
parser.add_argument('--kdamonds', metavar='<json/yaml string or file>',
help=' '.join([
'json or yaml format kdamonds specification to run DAMON for.',
'\'damo args damon\' can help generating it.']))
parser.add_argument(
'deducible_target', type=str, metavar='<deducible string>',
nargs='?',
help=' '.join([
'The implicit monitoring requests.',
'It could be a command, process id, special keywords, or full',
'DAMON parameters (same to that for --kdamonds)']))
def set_damon_params_argparser(parser, min_help):
set_monitoring_damos_common_args(parser, min_help)
set_monitoring_argparser(parser, min_help)
set_damos_argparser(parser, min_help)
set_misc_damon_params_argparser(parser)
set_monitoring_attrs_pinpoint_argparser(parser, hide_help=True)
def set_argparser(parser, add_record_options, min_help):
set_damon_params_argparser(parser, min_help)
if add_record_options:
parser.add_argument('-o', '--out', metavar='<file path>', type=str,
default='damon.data', help='output file path')
set_common_argparser(parser)
if min_help:
if parser.epilog is None:
parser.epilog = ''
parser.epilog += ' '.join([
"DAMON parameters options are also supported.",
"Do 'damo help damon_param_options -h' for the options."])