projects/linux·blob master · refs

391 lines · 15.1 KiB

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], 'max_age_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 '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 # 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 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
282def 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, # 500 ms
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 # test online commitment of minimum context.
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 # test obsolete_target.
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
390if __name__ == '__main__':
391 main()