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