1# SPDX-License-Identifier: GPL-2.0
2
3import os
4
5sysfs_root = '/sys/kernel/mm/damon/admin'
6
7def write_file(path, string):
8    "Returns error string if failed, or None otherwise"
9    string = '%s' % string
10    try:
11        with open(path, 'w') as f:
12            f.write(string)
13    except Exception as e:
14        return '%s' % e
15    return None
16
17def read_file(path):
18    '''Returns the read content and error string.  The read content is None if
19    the reading failed'''
20    try:
21        with open(path, 'r') as f:
22            return f.read(), None
23    except Exception as e:
24        return None, '%s' % e
25
26class DamosAccessPattern:
27    size = None
28    nr_accesses = None
29    age = None
30    scheme = None
31
32    def __init__(self, size=None, nr_accesses=None, age=None):
33        self.size = size
34        self.nr_accesses = nr_accesses
35        self.age = age
36
37        if self.size == None:
38            self.size = [0, 2**64 - 1]
39        if self.nr_accesses == None:
40            self.nr_accesses = [0, 2**64 - 1]
41        if self.age == None:
42            self.age = [0, 2**64 - 1]
43
44    def sysfs_dir(self):
45        return os.path.join(self.scheme.sysfs_dir(), 'access_pattern')
46
47    def stage(self):
48        err = write_file(
49                os.path.join(self.sysfs_dir(), 'sz', 'min'), self.size[0])
50        if err != None:
51            return err
52        err = write_file(
53                os.path.join(self.sysfs_dir(), 'sz', 'max'), self.size[1])
54        if err != None:
55            return err
56        err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'min'),
57                self.nr_accesses[0])
58        if err != None:
59            return err
60        err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'max'),
61                self.nr_accesses[1])
62        if err != None:
63            return err
64        err = write_file(
65                os.path.join(self.sysfs_dir(), 'age', 'min'), self.age[0])
66        if err != None:
67            return err
68        err = write_file(
69                os.path.join(self.sysfs_dir(), 'age', 'max'), self.age[1])
70        if err != None:
71            return err
72
73class DamosQuota:
74    sz = None                   # size quota, in bytes
75    ms = None                   # time quota
76    reset_interval_ms = None    # quota reset interval
77    scheme = None               # owner scheme
78
79    def __init__(self, sz=0, ms=0, reset_interval_ms=0):
80        self.sz = sz
81        self.ms = ms
82        self.reset_interval_ms = reset_interval_ms
83
84    def sysfs_dir(self):
85        return os.path.join(self.scheme.sysfs_dir(), 'quotas')
86
87    def stage(self):
88        err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz)
89        if err != None:
90            return err
91        err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms)
92        if err != None:
93            return err
94        err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'),
95                         self.reset_interval_ms)
96        if err != None:
97            return err
98
99class DamosStats:
100    nr_tried = None
101    sz_tried = None
102    nr_applied = None
103    sz_applied = None
104    qt_exceeds = None
105
106    def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds):
107        self.nr_tried = nr_tried
108        self.sz_tried = sz_tried
109        self.nr_applied = nr_applied
110        self.sz_applied = sz_applied
111        self.qt_exceeds = qt_exceeds
112
113class Damos:
114    action = None
115    access_pattern = None
116    quota = None
117    apply_interval_us = None
118    # todo: Support watermarks, stats, tried_regions
119    idx = None
120    context = None
121    tried_bytes = None
122    stats = None
123
124    def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
125                 quota=DamosQuota(), apply_interval_us=0):
126        self.action = action
127        self.access_pattern = access_pattern
128        self.access_pattern.scheme = self
129        self.quota = quota
130        self.quota.scheme = self
131        self.apply_interval_us = apply_interval_us
132
133    def sysfs_dir(self):
134        return os.path.join(
135                self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
136
137    def stage(self):
138        err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
139        if err != None:
140            return err
141        err = self.access_pattern.stage()
142        if err != None:
143            return err
144        err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'),
145                         '%d' % self.apply_interval_us)
146        if err != None:
147            return err
148
149        err = self.quota.stage()
150        if err != None:
151            return err
152
153        # disable watermarks
154        err = write_file(
155                os.path.join(self.sysfs_dir(), 'watermarks', 'metric'), 'none')
156        if err != None:
157            return err
158
159        # disable filters
160        err = write_file(
161                os.path.join(self.sysfs_dir(), 'filters', 'nr_filters'), '0')
162        if err != None:
163            return err
164
165class DamonTarget:
166    pid = None
167    # todo: Support target regions if test is made
168    idx = None
169    context = None
170
171    def __init__(self, pid):
172        self.pid = pid
173
174    def sysfs_dir(self):
175        return os.path.join(
176                self.context.sysfs_dir(), 'targets', '%d' % self.idx)
177
178    def stage(self):
179        err = write_file(
180                os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
181        if err != None:
182            return err
183        return write_file(
184                os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
185
186class DamonAttrs:
187    sample_us = None
188    aggr_us = None
189    update_us = None
190    min_nr_regions = None
191    max_nr_regions = None
192    context = None
193
194    def __init__(self, sample_us=5000, aggr_us=100000, update_us=1000000,
195            min_nr_regions=10, max_nr_regions=1000):
196        self.sample_us = sample_us
197        self.aggr_us = aggr_us
198        self.update_us = update_us
199        self.min_nr_regions = min_nr_regions
200        self.max_nr_regions = max_nr_regions
201
202    def interval_sysfs_dir(self):
203        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
204                'intervals')
205
206    def nr_regions_range_sysfs_dir(self):
207        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
208                'nr_regions')
209
210    def stage(self):
211        err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
212                self.sample_us)
213        if err != None:
214            return err
215        err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
216                self.aggr_us)
217        if err != None:
218            return err
219        err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
220                self.update_us)
221        if err != None:
222            return err
223
224        err = write_file(
225                os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
226                self.min_nr_regions)
227        if err != None:
228            return err
229
230        err = write_file(
231                os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
232                self.max_nr_regions)
233        if err != None:
234            return err
235
236class DamonCtx:
237    ops = None
238    monitoring_attrs = None
239    targets = None
240    schemes = None
241    kdamond = None
242    idx = None
243
244    def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
245            schemes=[]):
246        self.ops = ops
247        self.monitoring_attrs = monitoring_attrs
248        self.monitoring_attrs.context = self
249
250        self.targets = targets
251        for idx, target in enumerate(self.targets):
252            target.idx = idx
253            target.context = self
254
255        self.schemes = schemes
256        for idx, scheme in enumerate(self.schemes):
257            scheme.idx = idx
258            scheme.context = self
259
260    def sysfs_dir(self):
261        return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
262                '%d' % self.idx)
263
264    def stage(self):
265        err = write_file(
266                os.path.join(self.sysfs_dir(), 'operations'), self.ops)
267        if err != None:
268            return err
269        err = self.monitoring_attrs.stage()
270        if err != None:
271            return err
272
273        nr_targets_file = os.path.join(
274                self.sysfs_dir(), 'targets', 'nr_targets')
275        content, err = read_file(nr_targets_file)
276        if err != None:
277            return err
278        if int(content) != len(self.targets):
279            err = write_file(nr_targets_file, '%d' % len(self.targets))
280            if err != None:
281                return err
282        for target in self.targets:
283            err = target.stage()
284            if err != None:
285                return err
286
287        nr_schemes_file = os.path.join(
288                self.sysfs_dir(), 'schemes', 'nr_schemes')
289        content, err = read_file(nr_schemes_file)
290        if int(content) != len(self.schemes):
291            err = write_file(nr_schemes_file, '%d' % len(self.schemes))
292            if err != None:
293                return err
294        for scheme in self.schemes:
295            err = scheme.stage()
296            if err != None:
297                return err
298        return None
299
300class Kdamond:
301    state = None
302    pid = None
303    contexts = None
304    idx = None      # index of this kdamond between siblings
305    kdamonds = None # parent
306
307    def __init__(self, contexts=[]):
308        self.contexts = contexts
309        for idx, context in enumerate(self.contexts):
310            context.idx = idx
311            context.kdamond = self
312
313    def sysfs_dir(self):
314        return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
315
316    def start(self):
317        nr_contexts_file = os.path.join(self.sysfs_dir(),
318                'contexts', 'nr_contexts')
319        content, err = read_file(nr_contexts_file)
320        if err != None:
321            return err
322        if int(content) != len(self.contexts):
323            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
324            if err != None:
325                return err
326
327        for context in self.contexts:
328            err = context.stage()
329            if err != None:
330                return err
331        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
332        return err
333
334    def update_schemes_tried_bytes(self):
335        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
336                'update_schemes_tried_bytes')
337        if err != None:
338            return err
339        for context in self.contexts:
340            for scheme in context.schemes:
341                content, err = read_file(os.path.join(scheme.sysfs_dir(),
342                    'tried_regions', 'total_bytes'))
343                if err != None:
344                    return err
345                scheme.tried_bytes = int(content)
346
347    def update_schemes_stats(self):
348        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
349                'update_schemes_stats')
350        if err != None:
351            return err
352        for context in self.contexts:
353            for scheme in context.schemes:
354                stat_values = []
355                for stat in ['nr_tried', 'sz_tried', 'nr_applied',
356                             'sz_applied', 'qt_exceeds']:
357                    content, err = read_file(
358                            os.path.join(scheme.sysfs_dir(), 'stats', stat))
359                    if err != None:
360                        return err
361                    stat_values.append(int(content))
362                scheme.stats = DamosStats(*stat_values)
363
364class Kdamonds:
365    kdamonds = []
366
367    def __init__(self, kdamonds=[]):
368        self.kdamonds = kdamonds
369        for idx, kdamond in enumerate(self.kdamonds):
370            kdamond.idx = idx
371            kdamond.kdamonds = self
372
373    def sysfs_dir(self):
374        return os.path.join(sysfs_root, 'kdamonds')
375
376    def start(self):
377        err = write_file(os.path.join(self.sysfs_dir(),  'nr_kdamonds'),
378                '%s' % len(self.kdamonds))
379        if err != None:
380            return err
381        for kdamond in self.kdamonds:
382            err = kdamond.start()
383            if err != None:
384                return err
385        return None
386