xref: /linux/tools/testing/selftests/damon/sysfs.py (revision bba2c3615bd6cfee7456d1130f2e6b01b3f4e9ba)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import json
5import os
6import subprocess
7
8import _damon_sysfs
9
10def 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
27kdamonds = None
28def 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
35def assert_true(condition, expectation, status):
36    if condition is not True:
37        fail(expectation, status)
38
39def 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
51def 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
66def 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
90def 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
96def 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    # TODO: check memcg_path and memcg_id if type is memcg
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
111def 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], 'miaxage_region',
123                dump)
124
125def 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            'nohugeapge': 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    # TODO: test filters directory
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
153def 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
158def 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
181def assert_monitoring_target_committed(target, dump):
182    # target.pid is the pid "number", while dump['pid'] is 'struct pid'
183    # pointer, and hence cannot be compared.
184    assert_true(dump['obsolete'] == target.obsolete, 'target obsolete', dump)
185
186def 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
191def 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
203def assert_ctxs_committed(kdamonds):
204    ctxs_paused_for_dump = []
205    kdamonds_paused_for_dump = []
206    # pause for safe state dumping
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    # resume contexts paused for safe state dumping
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    # restore for comparison
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    # restore for the caller
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
253def main():
254    global kdamonds
255    kdamonds = _damon_sysfs.Kdamonds(
256            [_damon_sysfs.Kdamond(
257                contexts=[_damon_sysfs.DamonCtx(
258                    targets=[_damon_sysfs.DamonTarget(pid=-1)],
259                    schemes=[_damon_sysfs.Damos()],
260                    )])])
261    err = kdamonds.start()
262    if err is not None:
263        print('kdamond start failed: %s' % err)
264        exit(1)
265
266    assert_ctxs_committed(kdamonds)
267
268    context = _damon_sysfs.DamonCtx(
269            monitoring_attrs=_damon_sysfs.DamonAttrs(
270                sample_us=100000, aggr_us=2000000,
271                intervals_goal=_damon_sysfs.IntervalsGoal(
272                    access_bp=400, aggrs=3, min_sample_us=5000,
273                    max_sample_us=10000000),
274                update_us=2000000),
275            schemes=[_damon_sysfs.Damos(
276                action='pageout',
277                access_pattern=_damon_sysfs.DamosAccessPattern(
278                    size=[4096, 2**10],
279                    nr_accesses=[3, 317],
280                    age=[5,71]),
281                quota=_damon_sysfs.DamosQuota(
282                    sz=100*1024*1024, ms=100,
283                    goals=[_damon_sysfs.DamosQuotaGoal(
284                        metric='node_mem_used_bp',
285                        target_value=9950,
286                        nid=1)],
287                    goal_tuner='temporal',
288                    reset_interval_ms=1500,
289                    fail_charge_num=1,
290                    fail_charge_denom=4096,
291                    weight_sz_permil=20,
292                    weight_nr_accesses_permil=200,
293                    weight_age_permil=1000),
294                watermarks=_damon_sysfs.DamosWatermarks(
295                    metric = 'free_mem_rate', interval = 500000, # 500 ms
296                    high = 500, mid = 400, low = 50),
297                target_nid=1,
298                apply_interval_us=1000000,
299                dests=_damon_sysfs.DamosDests(
300                    dests=[_damon_sysfs.DamosDest(id=1, weight=30),
301                           _damon_sysfs.DamosDest(id=0, weight=70)]),
302                core_filters=[
303                    _damon_sysfs.DamosFilter(type_='addr', matching=True,
304                                             allow=False, addr_start=42,
305                                             addr_end=4242),
306                    ],
307                ops_filters=[
308                    _damon_sysfs.DamosFilter(type_='anon', matching=True,
309                                             allow=True),
310                    ],
311                )])
312    context.idx = 0
313    context.kdamond = kdamonds.kdamonds[0]
314    kdamonds.kdamonds[0].contexts = [context]
315    kdamonds.kdamonds[0].commit()
316
317    assert_ctxs_committed(kdamonds)
318
319    # test online commitment of minimum context.
320    context = _damon_sysfs.DamonCtx()
321    context.idx = 0
322    context.kdamond = kdamonds.kdamonds[0]
323    kdamonds.kdamonds[0].contexts = [context]
324    kdamonds.kdamonds[0].commit()
325
326    assert_ctxs_committed(kdamonds)
327
328    kdamonds.stop()
329
330    # test obsolete_target.
331    proc1 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
332                             stderr=subprocess.PIPE)
333    proc2 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
334                             stderr=subprocess.PIPE)
335    proc3 = subprocess.Popen(['sh'], stdout=subprocess.PIPE,
336                             stderr=subprocess.PIPE)
337    kdamonds = _damon_sysfs.Kdamonds(
338            [_damon_sysfs.Kdamond(
339                contexts=[_damon_sysfs.DamonCtx(
340                    ops='vaddr',
341                    targets=[
342                        _damon_sysfs.DamonTarget(pid=proc1.pid),
343                        _damon_sysfs.DamonTarget(pid=proc2.pid),
344                        _damon_sysfs.DamonTarget(pid=proc3.pid),
345                        ],
346                    schemes=[_damon_sysfs.Damos()],
347                    )])])
348    err = kdamonds.start()
349    if err is not None:
350        print('kdamond start failed: %s' % err)
351        exit(1)
352    kdamonds.kdamonds[0].contexts[0].targets[1].obsolete = True
353    kdamonds.kdamonds[0].contexts[0].pause = True
354    kdamonds.kdamonds[0].commit()
355    del kdamonds.kdamonds[0].contexts[0].targets[1]
356    assert_ctxs_committed(kdamonds)
357    kdamonds.stop()
358
359if __name__ == '__main__':
360    main()
361