xref: /linux/tools/testing/selftests/damon/_damon_sysfs.py (revision beb69e81724634063b9dbae4bc79e2e011fdeeb1)
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        if err is not None:
412            return err
413        self.pid, err = read_file(os.path.join(self.sysfs_dir(), 'pid'))
414        return err
415
416    def stop(self):
417        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off')
418        return err
419
420    def update_schemes_tried_regions(self):
421        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
422                         'update_schemes_tried_regions')
423        if err is not None:
424            return err
425        for context in self.contexts:
426            for scheme in context.schemes:
427                tried_regions = []
428                tried_regions_dir = os.path.join(
429                        scheme.sysfs_dir(), 'tried_regions')
430                region_indices = []
431                for filename in os.listdir(
432                        os.path.join(scheme.sysfs_dir(), 'tried_regions')):
433                    tried_region_dir = os.path.join(tried_regions_dir, filename)
434                    if not os.path.isdir(tried_region_dir):
435                        continue
436                    region_indices.append(int(filename))
437                for region_idx in sorted(region_indices):
438                    tried_region_dir = os.path.join(tried_regions_dir,
439                                                    '%d' % region_idx)
440                    region_values = []
441                    for f in ['start', 'end', 'nr_accesses', 'age']:
442                        content, err = read_file(
443                                os.path.join(tried_region_dir, f))
444                        if err is not None:
445                            return err
446                        region_values.append(int(content))
447                    tried_regions.append(DamosTriedRegion(*region_values))
448                scheme.tried_regions = tried_regions
449
450    def update_schemes_tried_bytes(self):
451        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
452                'update_schemes_tried_bytes')
453        if err is not None:
454            return err
455        for context in self.contexts:
456            for scheme in context.schemes:
457                content, err = read_file(os.path.join(scheme.sysfs_dir(),
458                    'tried_regions', 'total_bytes'))
459                if err is not None:
460                    return err
461                scheme.tried_bytes = int(content)
462
463    def update_schemes_stats(self):
464        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
465                'update_schemes_stats')
466        if err is not None:
467            return err
468        for context in self.contexts:
469            for scheme in context.schemes:
470                stat_values = []
471                for stat in ['nr_tried', 'sz_tried', 'nr_applied',
472                             'sz_applied', 'qt_exceeds']:
473                    content, err = read_file(
474                            os.path.join(scheme.sysfs_dir(), 'stats', stat))
475                    if err is not None:
476                        return err
477                    stat_values.append(int(content))
478                scheme.stats = DamosStats(*stat_values)
479
480    def update_schemes_effective_quotas(self):
481        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
482                         'update_schemes_effective_quotas')
483        if err is not None:
484            return err
485        for context in self.contexts:
486            for scheme in context.schemes:
487                for goal in scheme.quota.goals:
488                    content, err = read_file(
489                            os.path.join(scheme.quota.sysfs_dir(),
490                                         'effective_bytes'))
491                    if err is not None:
492                        return err
493                    goal.effective_bytes = int(content)
494        return None
495
496    def commit(self):
497        nr_contexts_file = os.path.join(self.sysfs_dir(),
498                'contexts', 'nr_contexts')
499        content, err = read_file(nr_contexts_file)
500        if err is not None:
501            return err
502        if int(content) != len(self.contexts):
503            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
504            if err is not None:
505                return err
506
507        for context in self.contexts:
508            err = context.stage()
509            if err is not None:
510                return err
511        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit')
512        return err
513
514
515    def commit_schemes_quota_goals(self):
516        for context in self.contexts:
517            for scheme in context.schemes:
518                for goal in scheme.quota.goals:
519                    err = goal.stage()
520                    if err is not None:
521                        print('commit_schemes_quota_goals failed stagign: %s'%
522                              err)
523                        exit(1)
524        return write_file(os.path.join(self.sysfs_dir(), 'state'),
525                         'commit_schemes_quota_goals')
526
527class Kdamonds:
528    kdamonds = []
529
530    def __init__(self, kdamonds=[]):
531        self.kdamonds = kdamonds
532        for idx, kdamond in enumerate(self.kdamonds):
533            kdamond.idx = idx
534            kdamond.kdamonds = self
535
536    def sysfs_dir(self):
537        return os.path.join(sysfs_root, 'kdamonds')
538
539    def start(self):
540        err = write_file(os.path.join(self.sysfs_dir(),  'nr_kdamonds'),
541                '%s' % len(self.kdamonds))
542        if err is not None:
543            return err
544        for kdamond in self.kdamonds:
545            err = kdamond.start()
546            if err is not None:
547                return err
548        return None
549
550    def stop(self):
551        for kdamond in self.kdamonds:
552            err = kdamond.stop()
553            if err is not None:
554                return err
555        return None
556