| 1 | |
| 2 | |
| 3 |
|
| 4 | import json
|
| 5 | import os
|
| 6 | import subprocess
|
| 7 |
|
| 8 | import _damon_sysfs
|
| 9 |
|
| 10 | def dump_damon_status_dict(pid):
|
| 11 | try:
|
| 12 | subprocess.check_output(['which', 'drgn'], stderr=subprocess.DEVNULL)
|
| 13 | except:
|
| 14 | return None, 'drgn not found'
|
| 15 | file_dir = os.path.dirname(os.path.abspath(__file__))
|
| 16 | dump_script = os.path.join(file_dir, 'drgn_dump_damon_status.py')
|
| 17 | rc = subprocess.call(['drgn', dump_script, pid, 'damon_dump_output'],
|
| 18 | stderr=subprocess.DEVNULL)
|
| 19 | if rc != 0:
|
| 20 | return None, 'drgn fail'
|
| 21 | try:
|
| 22 | with open('damon_dump_output', 'r') as f:
|
| 23 | return json.load(f), None
|
| 24 | except Exception as e:
|
| 25 | return None, 'json.load fail (%s)' % e
|
| 26 |
|
| 27 | kdamonds = None
|
| 28 | def fail(expectation, status):
|
| 29 | print('unexpected %s' % expectation)
|
| 30 | print(json.dumps(status, indent=4))
|
| 31 | if kdamonds is not None:
|
| 32 | kdamonds.stop()
|
| 33 | exit(1)
|
| 34 |
|
| 35 | def assert_true(condition, expectation, status):
|
| 36 | if condition is not True:
|
| 37 | fail(expectation, status)
|
| 38 |
|
| 39 | def assert_watermarks_committed(watermarks, dump):
|
| 40 | wmark_metric_val = {
|
| 41 | 'none': 0,
|
| 42 | 'free_mem_rate': 1,
|
| 43 | }
|
| 44 | assert_true(dump['metric'] == wmark_metric_val[watermarks.metric],
|
| 45 | 'metric', dump)
|
| 46 | assert_true(dump['interval'] == watermarks.interval, 'interval', dump)
|
| 47 | assert_true(dump['high'] == watermarks.high, 'high', dump)
|
| 48 | assert_true(dump['mid'] == watermarks.mid, 'mid', dump)
|
| 49 | assert_true(dump['low'] == watermarks.low, 'low', dump)
|
| 50 |
|
| 51 | def assert_quota_goal_committed(qgoal, dump):
|
| 52 | metric_val = {
|
| 53 | 'user_input': 0,
|
| 54 | 'some_mem_psi_us': 1,
|
| 55 | 'node_mem_used_bp': 2,
|
| 56 | 'node_mem_free_bp': 3,
|
| 57 | }
|
| 58 | assert_true(dump['metric'] == metric_val[qgoal.metric], 'metric', dump)
|
| 59 | assert_true(dump['target_value'] == qgoal.target_value, 'target_value',
|
| 60 | dump)
|
| 61 | if qgoal.metric == 'user_input':
|
| 62 | assert_true(dump['current_value'] == qgoal.current_value,
|
| 63 | 'current_value', dump)
|
| 64 | assert_true(dump['nid'] == qgoal.nid, 'nid', dump)
|
| 65 |
|
| 66 | def assert_quota_committed(quota, dump):
|
| 67 | assert_true(dump['reset_interval'] == quota.reset_interval_ms,
|
| 68 | 'reset_interval', dump)
|
| 69 | assert_true(dump['ms'] == quota.ms, 'ms', dump)
|
| 70 | assert_true(dump['sz'] == quota.sz, 'sz', dump)
|
| 71 | for idx, qgoal in enumerate(quota.goals):
|
| 72 | assert_quota_goal_committed(qgoal, dump['goals'][idx])
|
| 73 | tuner_val = {
|
| 74 | 'consist': 0,
|
| 75 | 'temporal': 1,
|
| 76 | }
|
| 77 | assert_true(dump['goal_tuner'] == tuner_val[quota.goal_tuner],
|
| 78 | 'goal_tuner', dump)
|
| 79 | assert_true(dump['fail_charge_num'] == quota.fail_charge_num,
|
| 80 | 'fail_charge_num', dump)
|
| 81 | assert_true(dump['fail_charge_denom'] == quota.fail_charge_denom,
|
| 82 | 'fail_charge_denom', dump)
|
| 83 | assert_true(dump['weight_sz'] == quota.weight_sz_permil, 'weight_sz', dump)
|
| 84 | assert_true(dump['weight_nr_accesses'] == quota.weight_nr_accesses_permil,
|
| 85 | 'weight_nr_accesses', dump)
|
| 86 | assert_true(
|
| 87 | dump['weight_age'] == quota.weight_age_permil, 'weight_age', dump)
|
| 88 |
|
| 89 |
|
| 90 | def assert_migrate_dests_committed(dests, dump):
|
| 91 | assert_true(dump['nr_dests'] == len(dests.dests), 'nr_dests', dump)
|
| 92 | for idx, dest in enumerate(dests.dests):
|
| 93 | assert_true(dump['node_id_arr'][idx] == dest.id, 'node_id', dump)
|
| 94 | assert_true(dump['weight_arr'][idx] == dest.weight, 'weight', dump)
|
| 95 |
|
| 96 | def assert_filter_committed(filter_, dump):
|
| 97 | assert_true(filter_.type_ == dump['type'], 'type', dump)
|
| 98 | assert_true(filter_.matching == dump['matching'], 'matching', dump)
|
| 99 | assert_true(filter_.allow == dump['allow'], 'allow', dump)
|
| 100 | |
| 101 | if filter_.type_ == 'addr':
|
| 102 | assert_true([filter_.addr_start, filter_.addr_end] ==
|
| 103 | dump['addr_range'], 'addr_range', dump)
|
| 104 | elif filter_.type_ == 'target':
|
| 105 | assert_true(filter_.target_idx == dump['target_idx'], 'target_idx',
|
| 106 | dump)
|
| 107 | elif filter_.type_ == 'hugepage_size':
|
| 108 | assert_true([filter_.min_, filter_.max_] == dump['sz_range'],
|
| 109 | 'sz_range', dump)
|
| 110 |
|
| 111 | def assert_access_pattern_committed(pattern, dump):
|
| 112 | assert_true(dump['min_sz_region'] == pattern.size[0], 'min_sz_region',
|
| 113 | dump)
|
| 114 | assert_true(dump['max_sz_region'] == pattern.size[1], 'max_sz_region',
|
| 115 | dump)
|
| 116 | assert_true(dump['min_nr_accesses'] == pattern.nr_accesses[0],
|
| 117 | 'min_nr_accesses', dump)
|
| 118 | assert_true(dump['max_nr_accesses'] == pattern.nr_accesses[1],
|
| 119 | 'max_nr_accesses', dump)
|
| 120 | assert_true(dump['min_age_region'] == pattern.age[0], 'min_age_region',
|
| 121 | dump)
|
| 122 | assert_true(dump['max_age_region'] == pattern.age[1], 'max_age_region',
|
| 123 | dump)
|
| 124 |
|
| 125 | def assert_scheme_committed(scheme, dump):
|
| 126 | assert_access_pattern_committed(scheme.access_pattern, dump['pattern'])
|
| 127 | action_val = {
|
| 128 | 'willneed': 0,
|
| 129 | 'cold': 1,
|
| 130 | 'pageout': 2,
|
| 131 | 'hugepage': 3,
|
| 132 | 'nohugepage': 4,
|
| 133 | 'collapse': 5,
|
| 134 | 'lru_prio': 6,
|
| 135 | 'lru_deprio': 7,
|
| 136 | 'migrate_hot': 8,
|
| 137 | 'migrate_cold': 9,
|
| 138 | 'stat': 10,
|
| 139 | }
|
| 140 | assert_true(dump['action'] == action_val[scheme.action], 'action', dump)
|
| 141 | assert_true(dump['apply_interval_us'] == scheme. apply_interval_us,
|
| 142 | 'apply_interval_us', dump)
|
| 143 | assert_true(dump['target_nid'] == scheme.target_nid, 'target_nid', dump)
|
| 144 | assert_migrate_dests_committed(scheme.dests, dump['migrate_dests'])
|
| 145 | assert_quota_committed(scheme.quota, dump['quota'])
|
| 146 | assert_watermarks_committed(scheme.watermarks, dump['wmarks'])
|
| 147 | |
| 148 | for idx, f in enumerate(scheme.core_filters.filters):
|
| 149 | assert_filter_committed(f, dump['core_filters'][idx])
|
| 150 | for idx, f in enumerate(scheme.ops_filters.filters):
|
| 151 | assert_filter_committed(f, dump['ops_filters'][idx])
|
| 152 |
|
| 153 | def assert_schemes_committed(schemes, dump):
|
| 154 | assert_true(len(schemes) == len(dump), 'len_schemes', dump)
|
| 155 | for idx, scheme in enumerate(schemes):
|
| 156 | assert_scheme_committed(scheme, dump[idx])
|
| 157 |
|
| 158 | def assert_monitoring_attrs_committed(attrs, dump):
|
| 159 | assert_true(dump['sample_interval'] == attrs.sample_us, 'sample_interval',
|
| 160 | dump)
|
| 161 | assert_true(dump['aggr_interval'] == attrs.aggr_us, 'aggr_interval', dump)
|
| 162 | assert_true(dump['intervals_goal']['access_bp'] ==
|
| 163 | attrs.intervals_goal.access_bp, 'access_bp',
|
| 164 | dump['intervals_goal'])
|
| 165 | assert_true(dump['intervals_goal']['aggrs'] == attrs.intervals_goal.aggrs,
|
| 166 | 'aggrs', dump['intervals_goal'])
|
| 167 | assert_true(dump['intervals_goal']['min_sample_us'] ==
|
| 168 | attrs.intervals_goal.min_sample_us, 'min_sample_us',
|
| 169 | dump['intervals_goal'])
|
| 170 | assert_true(dump['intervals_goal']['max_sample_us'] ==
|
| 171 | attrs.intervals_goal.max_sample_us, 'max_sample_us',
|
| 172 | dump['intervals_goal'])
|
| 173 |
|
| 174 | assert_true(dump['ops_update_interval'] == attrs.update_us,
|
| 175 | 'ops_update_interval', dump)
|
| 176 | assert_true(dump['min_nr_regions'] == attrs.min_nr_regions,
|
| 177 | 'min_nr_regions', dump)
|
| 178 | assert_true(dump['max_nr_regions'] == attrs.max_nr_regions,
|
| 179 | 'max_nr_regions', dump)
|
| 180 |
|
| 181 | def assert_monitoring_target_committed(target, dump):
|
| 182 | |
| 183 | |
| 184 | assert_true(dump['obsolete'] == target.obsolete, 'target obsolete', dump)
|
| 185 |
|
| 186 | def assert_monitoring_targets_committed(targets, dump):
|
| 187 | assert_true(len(targets) == len(dump), 'len_targets', dump)
|
| 188 | for idx, target in enumerate(targets):
|
| 189 | assert_monitoring_target_committed(target, dump[idx])
|
| 190 |
|
| 191 | def assert_ctx_committed(ctx, dump):
|
| 192 | ops_val = {
|
| 193 | 'vaddr': 0,
|
| 194 | 'fvaddr': 1,
|
| 195 | 'paddr': 2,
|
| 196 | }
|
| 197 | assert_true(dump['ops']['id'] == ops_val[ctx.ops], 'ops_id', dump)
|
| 198 | assert_monitoring_attrs_committed(ctx.monitoring_attrs, dump['attrs'])
|
| 199 | assert_monitoring_targets_committed(ctx.targets, dump['adaptive_targets'])
|
| 200 | assert_schemes_committed(ctx.schemes, dump['schemes'])
|
| 201 | assert_true(dump['pause'] == ctx.pause, 'pause', dump)
|
| 202 |
|
| 203 | def assert_ctxs_committed(kdamonds):
|
| 204 | ctxs_paused_for_dump = []
|
| 205 | kdamonds_paused_for_dump = []
|
| 206 | |
| 207 | for kd in kdamonds.kdamonds:
|
| 208 | for ctx in kd.contexts:
|
| 209 | if ctx.pause is False:
|
| 210 | ctx.pause = True
|
| 211 | ctxs_paused_for_dump.append(ctx)
|
| 212 | if not kd in kdamonds_paused_for_dump:
|
| 213 | kdamonds_paused_for_dump.append(kd)
|
| 214 | if kd in kdamonds_paused_for_dump:
|
| 215 | err = kd.commit()
|
| 216 | if err is not None:
|
| 217 | print('pause fail (%s)' % err)
|
| 218 | kdamonds.stop()
|
| 219 | exit(1)
|
| 220 |
|
| 221 | status, err = dump_damon_status_dict(kdamonds.kdamonds[0].pid)
|
| 222 | if err is not None:
|
| 223 | print(err)
|
| 224 | kdamonds.stop()
|
| 225 | exit(1)
|
| 226 |
|
| 227 | |
| 228 | for ctx in ctxs_paused_for_dump:
|
| 229 | ctx.pause = False
|
| 230 | for kd in kdamonds_paused_for_dump:
|
| 231 | err = kd.commit()
|
| 232 | if err is not None:
|
| 233 | print('resume fail (%s)' % err)
|
| 234 | kdamonds.stop()
|
| 235 | exit(1)
|
| 236 |
|
| 237 | |
| 238 | for ctx in ctxs_paused_for_dump:
|
| 239 | ctx.pause = True
|
| 240 |
|
| 241 | ctxs = kdamonds.kdamonds[0].contexts
|
| 242 | dump = status['contexts']
|
| 243 | assert_true(len(ctxs) == len(dump), 'ctxs length', dump)
|
| 244 | for idx, ctx in enumerate(ctxs):
|
| 245 | assert_ctx_committed(ctx, dump[idx])
|
| 246 |
|
| 247 | |
| 248 | for kd in kdamonds.kdamonds:
|
| 249 | for ctx in kd.contexts:
|
| 250 | if ctx in ctxs_paused_for_dump:
|
| 251 | ctx.pause = False
|
| 252 |
|
| 253 | def test_memcg_filter_memcg_path_staging():
|
| 254 | global kdamonds
|
| 255 | memcg_filter = _damon_sysfs.DamosFilter(
|
| 256 | type_='memcg', matching=True, allow=True, memcg_path='/')
|
| 257 | kdamonds = _damon_sysfs.Kdamonds(
|
| 258 | [_damon_sysfs.Kdamond(
|
| 259 | contexts=[_damon_sysfs.DamonCtx(
|
| 260 | targets=[_damon_sysfs.DamonTarget(pid=-1)],
|
| 261 | schemes=[_damon_sysfs.Damos(
|
| 262 | ops_filters=[memcg_filter])],
|
| 263 | )])])
|
| 264 | kdamonds.start()
|
| 265 |
|
| 266 | shown, rd_err = _damon_sysfs.read_file(
|
| 267 | os.path.join(memcg_filter.sysfs_dir(), 'memcg_path'))
|
| 268 | if rd_err is not None:
|
| 269 | print('memcg_path staging: sysfs read (%s)' % rd_err)
|
| 270 | kdamonds.stop()
|
| 271 | exit(1)
|
| 272 | if shown.rstrip('\n') != memcg_filter.memcg_path:
|
| 273 | print('memcg_path staging: memcg_path readback '
|
| 274 | '(shown=%s, expected=%s)' %
|
| 275 | (shown.rstrip('\n'), memcg_filter.memcg_path))
|
| 276 | kdamonds.stop()
|
| 277 | exit(1)
|
| 278 |
|
| 279 | kdamonds.stop()
|
| 280 | kdamonds = None
|
| 281 |
|
| 282 | def main():
|
| 283 | global kdamonds
|
| 284 | kdamonds = _damon_sysfs.Kdamonds(
|
| 285 | [_damon_sysfs.Kdamond(
|
| 286 | contexts=[_damon_sysfs.DamonCtx(
|
| 287 | targets=[_damon_sysfs.DamonTarget(pid=-1)],
|
| 288 | schemes=[_damon_sysfs.Damos()],
|
| 289 | )])])
|
| 290 | err = kdamonds.start()
|
| 291 | if err is not None:
|
| 292 | print('kdamond start failed: %s' % err)
|
| 293 | exit(1)
|
| 294 |
|
| 295 | assert_ctxs_committed(kdamonds)
|
| 296 |
|
| 297 | context = _damon_sysfs.DamonCtx(
|
| 298 | monitoring_attrs=_damon_sysfs.DamonAttrs(
|
| 299 | sample_us=100000, aggr_us=2000000,
|
| 300 | intervals_goal=_damon_sysfs.IntervalsGoal(
|
| 301 | access_bp=400, aggrs=3, min_sample_us=5000,
|
| 302 | max_sample_us=10000000),
|
| 303 | update_us=2000000),
|
| 304 | schemes=[_damon_sysfs.Damos(
|
| 305 | action='pageout',
|
| 306 | access_pattern=_damon_sysfs.DamosAccessPattern(
|
| 307 | size=[4096, 2**10],
|
| 308 | nr_accesses=[3, 317],
|
| 309 | age=[5,71]),
|
| 310 | quota=_damon_sysfs.DamosQuota(
|
| 311 | sz=100*1024*1024, ms=100,
|
| 312 | goals=[_damon_sysfs.DamosQuotaGoal(
|
| 313 | metric='node_mem_used_bp',
|
| 314 | target_value=9950,
|
| 315 | nid=1)],
|
| 316 | goal_tuner='temporal',
|
| 317 | reset_interval_ms=1500,
|
| 318 | fail_charge_num=1,
|
| 319 | fail_charge_denom=4096,
|
| 320 | weight_sz_permil=20,
|
| 321 | weight_nr_accesses_permil=200,
|
| 322 | weight_age_permil=1000),
|
| 323 | watermarks=_damon_sysfs.DamosWatermarks(
|
| 324 | metric = 'free_mem_rate', interval = 500000, |
| 325 | high = 500, mid = 400, low = 50),
|
| 326 | target_nid=1,
|
| 327 | apply_interval_us=1000000,
|
| 328 | dests=_damon_sysfs.DamosDests(
|
| 329 | dests=[_damon_sysfs.DamosDest(id=1, weight=30),
|
| 330 | _damon_sysfs.DamosDest(id=0, weight=70)]),
|
| 331 | core_filters=[
|
| 332 | _damon_sysfs.DamosFilter(type_='addr', matching=True,
|
| 333 | allow=False, addr_start=42,
|
| 334 | addr_end=4242),
|
| 335 | ],
|
| 336 | ops_filters=[
|
| 337 | _damon_sysfs.DamosFilter(type_='anon', matching=True,
|
| 338 | allow=True),
|
| 339 | ],
|
| 340 | )])
|
| 341 | context.idx = 0
|
| 342 | context.kdamond = kdamonds.kdamonds[0]
|
| 343 | kdamonds.kdamonds[0].contexts = [context]
|
| 344 | kdamonds.kdamonds[0].commit()
|
| 345 |
|
| 346 | assert_ctxs_committed(kdamonds)
|
| 347 |
|
| 348 | |
| 349 | context = _damon_sysfs.DamonCtx()
|
| 350 | context.idx = 0
|
| 351 | context.kdamond = kdamonds.kdamonds[0]
|
| 352 | kdamonds.kdamonds[0].contexts = [context]
|
| 353 | kdamonds.kdamonds[0].commit()
|
| 354 |
|
| 355 | assert_ctxs_committed(kdamonds)
|
| 356 |
|
| 357 | kdamonds.stop()
|
| 358 |
|
| 359 | |
| 360 | proc1 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
|
| 361 | stderr=subprocess.PIPE)
|
| 362 | proc2 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
|
| 363 | stderr=subprocess.PIPE)
|
| 364 | proc3 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
|
| 365 | stderr=subprocess.PIPE)
|
| 366 | kdamonds = _damon_sysfs.Kdamonds(
|
| 367 | [_damon_sysfs.Kdamond(
|
| 368 | contexts=[_damon_sysfs.DamonCtx(
|
| 369 | ops='vaddr',
|
| 370 | targets=[
|
| 371 | _damon_sysfs.DamonTarget(pid=proc1.pid),
|
| 372 | _damon_sysfs.DamonTarget(pid=proc2.pid),
|
| 373 | _damon_sysfs.DamonTarget(pid=proc3.pid),
|
| 374 | ],
|
| 375 | schemes=[_damon_sysfs.Damos()],
|
| 376 | )])])
|
| 377 | err = kdamonds.start()
|
| 378 | if err is not None:
|
| 379 | print('kdamond start failed: %s' % err)
|
| 380 | exit(1)
|
| 381 | kdamonds.kdamonds[0].contexts[0].targets[1].obsolete = True
|
| 382 | kdamonds.kdamonds[0].contexts[0].pause = True
|
| 383 | kdamonds.kdamonds[0].commit()
|
| 384 | del kdamonds.kdamonds[0].contexts[0].targets[1]
|
| 385 | assert_ctxs_committed(kdamonds)
|
| 386 | kdamonds.stop()
|
| 387 |
|
| 388 | test_memcg_filter_memcg_path_staging()
|
| 389 |
|
| 390 | if __name__ == '__main__':
|
| 391 | main()
|