xref: /linux/tools/testing/selftests/damon/_damon_sysfs.py (revision fd1f8473503e5bf897bd3e8efe3545c0352954e6)
1# SPDX-License-Identifier: GPL-2.0
2
3import os
4
5ksft_skip=4
6
7sysfs_root = None
8with open('/proc/mounts', 'r') as f:
9    for line in f:
10        dev_name, mount_point, dev_fs = line.split()[:3]
11        if dev_fs == 'sysfs':
12            sysfs_root = '%s/kernel/mm/damon/admin' % mount_point
13            break
14if sysfs_root is None:
15    print('Seems sysfs not mounted?')
16    exit(ksft_skip)
17
18if not os.path.exists(sysfs_root):
19    print('Seems DAMON disabled?')
20    exit(ksft_skip)
21
22def write_file(path, string):
23    "Returns error string if failed, or None otherwise"
24    string = '%s' % string
25    try:
26        with open(path, 'w') as f:
27            f.write(string)
28    except Exception as e:
29        return '%s' % e
30    return None
31
32def read_file(path):
33    '''Returns the read content and error string.  The read content is None if
34    the reading failed'''
35    try:
36        with open(path, 'r') as f:
37            return f.read(), None
38    except Exception as e:
39        return None, '%s' % e
40
41class DamosAccessPattern:
42    size = None
43    nr_accesses = None
44    age = None
45    scheme = None
46
47    def __init__(self, size=None, nr_accesses=None, age=None):
48        self.size = size
49        self.nr_accesses = nr_accesses
50        self.age = age
51
52        if self.size is None:
53            self.size = [0, 2**64 - 1]
54        if self.nr_accesses is None:
55            self.nr_accesses = [0, 2**64 - 1]
56        if self.age is None:
57            self.age = [0, 2**64 - 1]
58
59    def sysfs_dir(self):
60        return os.path.join(self.scheme.sysfs_dir(), 'access_pattern')
61
62    def stage(self):
63        err = write_file(
64                os.path.join(self.sysfs_dir(), 'sz', 'min'), self.size[0])
65        if err is not None:
66            return err
67        err = write_file(
68                os.path.join(self.sysfs_dir(), 'sz', 'max'), self.size[1])
69        if err is not None:
70            return err
71        err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'min'),
72                self.nr_accesses[0])
73        if err is not None:
74            return err
75        err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'max'),
76                self.nr_accesses[1])
77        if err is not None:
78            return err
79        err = write_file(
80                os.path.join(self.sysfs_dir(), 'age', 'min'), self.age[0])
81        if err is not None:
82            return err
83        err = write_file(
84                os.path.join(self.sysfs_dir(), 'age', 'max'), self.age[1])
85        if err is not None:
86            return err
87
88qgoal_metric_user_input = 'user_input'
89qgoal_metric_some_mem_psi_us = 'some_mem_psi_us'
90qgoal_metrics = [qgoal_metric_user_input, qgoal_metric_some_mem_psi_us]
91
92class DamosQuotaGoal:
93    metric = None
94    target_value = None
95    current_value = None
96    effective_bytes = None
97    quota = None            # owner quota
98    idx = None
99
100    def __init__(self, metric, target_value=10000, current_value=0):
101        self.metric = metric
102        self.target_value = target_value
103        self.current_value = current_value
104
105    def sysfs_dir(self):
106        return os.path.join(self.quota.sysfs_dir(), 'goals', '%d' % self.idx)
107
108    def stage(self):
109        err = write_file(os.path.join(self.sysfs_dir(), 'target_metric'),
110                         self.metric)
111        if err is not None:
112            return err
113        err = write_file(os.path.join(self.sysfs_dir(), 'target_value'),
114                         self.target_value)
115        if err is not None:
116            return err
117        err = write_file(os.path.join(self.sysfs_dir(), 'current_value'),
118                         self.current_value)
119        if err is not None:
120            return err
121        return None
122
123class DamosQuota:
124    sz = None                   # size quota, in bytes
125    ms = None                   # time quota
126    goals = None                # quota goals
127    reset_interval_ms = None    # quota reset interval
128    scheme = None               # owner scheme
129
130    def __init__(self, sz=0, ms=0, goals=None, reset_interval_ms=0):
131        self.sz = sz
132        self.ms = ms
133        self.reset_interval_ms = reset_interval_ms
134        self.goals = goals if goals is not None else []
135        for idx, goal in enumerate(self.goals):
136            goal.idx = idx
137            goal.quota = self
138
139    def sysfs_dir(self):
140        return os.path.join(self.scheme.sysfs_dir(), 'quotas')
141
142    def stage(self):
143        err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz)
144        if err is not None:
145            return err
146        err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms)
147        if err is not None:
148            return err
149        err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'),
150                         self.reset_interval_ms)
151        if err is not None:
152            return err
153
154        nr_goals_file = os.path.join(self.sysfs_dir(), 'goals', 'nr_goals')
155        content, err = read_file(nr_goals_file)
156        if err is not None:
157            return err
158        if int(content) != len(self.goals):
159            err = write_file(nr_goals_file, len(self.goals))
160            if err is not None:
161                return err
162        for goal in self.goals:
163            err = goal.stage()
164            if err is not None:
165                return err
166        return None
167
168class DamosStats:
169    nr_tried = None
170    sz_tried = None
171    nr_applied = None
172    sz_applied = None
173    qt_exceeds = None
174
175    def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds):
176        self.nr_tried = nr_tried
177        self.sz_tried = sz_tried
178        self.nr_applied = nr_applied
179        self.sz_applied = sz_applied
180        self.qt_exceeds = qt_exceeds
181
182class DamosTriedRegion:
183    def __init__(self, start, end, nr_accesses, age):
184        self.start = start
185        self.end = end
186        self.nr_accesses = nr_accesses
187        self.age = age
188
189class Damos:
190    action = None
191    access_pattern = None
192    quota = None
193    apply_interval_us = None
194    # todo: Support watermarks, stats
195    idx = None
196    context = None
197    tried_bytes = None
198    stats = None
199    tried_regions = None
200
201    def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
202                 quota=DamosQuota(), apply_interval_us=0):
203        self.action = action
204        self.access_pattern = access_pattern
205        self.access_pattern.scheme = self
206        self.quota = quota
207        self.quota.scheme = self
208        self.apply_interval_us = apply_interval_us
209
210    def sysfs_dir(self):
211        return os.path.join(
212                self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
213
214    def stage(self):
215        err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
216        if err is not None:
217            return err
218        err = self.access_pattern.stage()
219        if err is not None:
220            return err
221        err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'),
222                         '%d' % self.apply_interval_us)
223        if err is not None:
224            return err
225
226        err = self.quota.stage()
227        if err is not None:
228            return err
229
230        # disable watermarks
231        err = write_file(
232                os.path.join(self.sysfs_dir(), 'watermarks', 'metric'), 'none')
233        if err is not None:
234            return err
235
236        # disable filters
237        err = write_file(
238                os.path.join(self.sysfs_dir(), 'filters', 'nr_filters'), '0')
239        if err is not None:
240            return err
241
242class DamonTarget:
243    pid = None
244    # todo: Support target regions if test is made
245    idx = None
246    context = None
247
248    def __init__(self, pid):
249        self.pid = pid
250
251    def sysfs_dir(self):
252        return os.path.join(
253                self.context.sysfs_dir(), 'targets', '%d' % self.idx)
254
255    def stage(self):
256        err = write_file(
257                os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
258        if err is not None:
259            return err
260        return write_file(
261                os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
262
263class DamonAttrs:
264    sample_us = None
265    aggr_us = None
266    update_us = None
267    min_nr_regions = None
268    max_nr_regions = None
269    context = None
270
271    def __init__(self, sample_us=5000, aggr_us=100000, update_us=1000000,
272            min_nr_regions=10, max_nr_regions=1000):
273        self.sample_us = sample_us
274        self.aggr_us = aggr_us
275        self.update_us = update_us
276        self.min_nr_regions = min_nr_regions
277        self.max_nr_regions = max_nr_regions
278
279    def interval_sysfs_dir(self):
280        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
281                'intervals')
282
283    def nr_regions_range_sysfs_dir(self):
284        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
285                'nr_regions')
286
287    def stage(self):
288        err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
289                self.sample_us)
290        if err is not None:
291            return err
292        err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
293                self.aggr_us)
294        if err is not None:
295            return err
296        err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
297                self.update_us)
298        if err is not None:
299            return err
300
301        err = write_file(
302                os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
303                self.min_nr_regions)
304        if err is not None:
305            return err
306
307        err = write_file(
308                os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
309                self.max_nr_regions)
310        if err is not None:
311            return err
312
313class DamonCtx:
314    ops = None
315    monitoring_attrs = None
316    targets = None
317    schemes = None
318    kdamond = None
319    idx = None
320
321    def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
322            schemes=[]):
323        self.ops = ops
324        self.monitoring_attrs = monitoring_attrs
325        self.monitoring_attrs.context = self
326
327        self.targets = targets
328        for idx, target in enumerate(self.targets):
329            target.idx = idx
330            target.context = self
331
332        self.schemes = schemes
333        for idx, scheme in enumerate(self.schemes):
334            scheme.idx = idx
335            scheme.context = self
336
337    def sysfs_dir(self):
338        return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
339                '%d' % self.idx)
340
341    def stage(self):
342        err = write_file(
343                os.path.join(self.sysfs_dir(), 'operations'), self.ops)
344        if err is not None:
345            return err
346        err = self.monitoring_attrs.stage()
347        if err is not None:
348            return err
349
350        nr_targets_file = os.path.join(
351                self.sysfs_dir(), 'targets', 'nr_targets')
352        content, err = read_file(nr_targets_file)
353        if err is not None:
354            return err
355        if int(content) != len(self.targets):
356            err = write_file(nr_targets_file, '%d' % len(self.targets))
357            if err is not None:
358                return err
359        for target in self.targets:
360            err = target.stage()
361            if err is not None:
362                return err
363
364        nr_schemes_file = os.path.join(
365                self.sysfs_dir(), 'schemes', 'nr_schemes')
366        content, err = read_file(nr_schemes_file)
367        if err is not None:
368            return err
369        if int(content) != len(self.schemes):
370            err = write_file(nr_schemes_file, '%d' % len(self.schemes))
371            if err is not None:
372                return err
373        for scheme in self.schemes:
374            err = scheme.stage()
375            if err is not None:
376                return err
377        return None
378
379class Kdamond:
380    state = None
381    pid = None
382    contexts = None
383    idx = None      # index of this kdamond between siblings
384    kdamonds = None # parent
385
386    def __init__(self, contexts=[]):
387        self.contexts = contexts
388        for idx, context in enumerate(self.contexts):
389            context.idx = idx
390            context.kdamond = self
391
392    def sysfs_dir(self):
393        return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
394
395    def start(self):
396        nr_contexts_file = os.path.join(self.sysfs_dir(),
397                'contexts', 'nr_contexts')
398        content, err = read_file(nr_contexts_file)
399        if err is not None:
400            return err
401        if int(content) != len(self.contexts):
402            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
403            if err is not None:
404                return err
405
406        for context in self.contexts:
407            err = context.stage()
408            if err is not None:
409                return err
410        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
411        return err
412
413    def stop(self):
414        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off')
415        return err
416
417    def update_schemes_tried_regions(self):
418        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
419                         'update_schemes_tried_regions')
420        if err is not None:
421            return err
422        for context in self.contexts:
423            for scheme in context.schemes:
424                tried_regions = []
425                tried_regions_dir = os.path.join(
426                        scheme.sysfs_dir(), 'tried_regions')
427                region_indices = []
428                for filename in os.listdir(
429                        os.path.join(scheme.sysfs_dir(), 'tried_regions')):
430                    tried_region_dir = os.path.join(tried_regions_dir, filename)
431                    if not os.path.isdir(tried_region_dir):
432                        continue
433                    region_indices.append(int(filename))
434                for region_idx in sorted(region_indices):
435                    tried_region_dir = os.path.join(tried_regions_dir,
436                                                    '%d' % region_idx)
437                    region_values = []
438                    for f in ['start', 'end', 'nr_accesses', 'age']:
439                        content, err = read_file(
440                                os.path.join(tried_region_dir, f))
441                        if err is not None:
442                            return err
443                        region_values.append(int(content))
444                    tried_regions.append(DamosTriedRegion(*region_values))
445                scheme.tried_regions = tried_regions
446
447    def update_schemes_tried_bytes(self):
448        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
449                'update_schemes_tried_bytes')
450        if err is not None:
451            return err
452        for context in self.contexts:
453            for scheme in context.schemes:
454                content, err = read_file(os.path.join(scheme.sysfs_dir(),
455                    'tried_regions', 'total_bytes'))
456                if err is not None:
457                    return err
458                scheme.tried_bytes = int(content)
459
460    def update_schemes_stats(self):
461        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
462                'update_schemes_stats')
463        if err is not None:
464            return err
465        for context in self.contexts:
466            for scheme in context.schemes:
467                stat_values = []
468                for stat in ['nr_tried', 'sz_tried', 'nr_applied',
469                             'sz_applied', 'qt_exceeds']:
470                    content, err = read_file(
471                            os.path.join(scheme.sysfs_dir(), 'stats', stat))
472                    if err is not None:
473                        return err
474                    stat_values.append(int(content))
475                scheme.stats = DamosStats(*stat_values)
476
477    def update_schemes_effective_quotas(self):
478        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
479                         'update_schemes_effective_quotas')
480        if err is not None:
481            return err
482        for context in self.contexts:
483            for scheme in context.schemes:
484                for goal in scheme.quota.goals:
485                    content, err = read_file(
486                            os.path.join(scheme.quota.sysfs_dir(),
487                                         'effective_bytes'))
488                    if err is not None:
489                        return err
490                    goal.effective_bytes = int(content)
491        return None
492
493    def commit(self):
494        nr_contexts_file = os.path.join(self.sysfs_dir(),
495                'contexts', 'nr_contexts')
496        content, err = read_file(nr_contexts_file)
497        if err is not None:
498            return err
499        if int(content) != len(self.contexts):
500            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
501            if err is not None:
502                return err
503
504        for context in self.contexts:
505            err = context.stage()
506            if err is not None:
507                return err
508        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit')
509        return err
510
511
512    def commit_schemes_quota_goals(self):
513        for context in self.contexts:
514            for scheme in context.schemes:
515                for goal in scheme.quota.goals:
516                    err = goal.stage()
517                    if err is not None:
518                        print('commit_schemes_quota_goals failed stagign: %s'%
519                              err)
520                        exit(1)
521        return write_file(os.path.join(self.sysfs_dir(), 'state'),
522                         'commit_schemes_quota_goals')
523
524class Kdamonds:
525    kdamonds = []
526
527    def __init__(self, kdamonds=[]):
528        self.kdamonds = kdamonds
529        for idx, kdamond in enumerate(self.kdamonds):
530            kdamond.idx = idx
531            kdamond.kdamonds = self
532
533    def sysfs_dir(self):
534        return os.path.join(sysfs_root, 'kdamonds')
535
536    def start(self):
537        err = write_file(os.path.join(self.sysfs_dir(),  'nr_kdamonds'),
538                '%s' % len(self.kdamonds))
539        if err is not None:
540            return err
541        for kdamond in self.kdamonds:
542            err = kdamond.start()
543            if err is not None:
544                return err
545        return None
546
547    def stop(self):
548        for kdamond in self.kdamonds:
549            err = kdamond.stop()
550            if err is not None:
551                return err
552        return None
553