xref: /linux/tools/testing/selftests/damon/_damon_sysfs.py (revision d5756953dd5d950940459f963cb7141c4b8d6e64)
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**32 - 1]
56        if self.age is None:
57            self.age = [0, 2**32 - 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    nid = None
97    effective_bytes = None
98    quota = None            # owner quota
99    idx = None
100
101    def __init__(self, metric, target_value=10000, current_value=0, nid=0):
102        self.metric = metric
103        self.target_value = target_value
104        self.current_value = current_value
105        self.nid = nid
106
107    def sysfs_dir(self):
108        return os.path.join(self.quota.sysfs_dir(), 'goals', '%d' % self.idx)
109
110    def stage(self):
111        err = write_file(os.path.join(self.sysfs_dir(), 'target_metric'),
112                         self.metric)
113        if err is not None:
114            return err
115        err = write_file(os.path.join(self.sysfs_dir(), 'target_value'),
116                         self.target_value)
117        if err is not None:
118            return err
119        err = write_file(os.path.join(self.sysfs_dir(), 'current_value'),
120                         self.current_value)
121        if err is not None:
122            return err
123        err = write_file(os.path.join(self.sysfs_dir(), 'nid'), self.nid)
124        if err is not None:
125            return err
126
127        return None
128
129class DamosQuota:
130    sz = None                   # size quota, in bytes
131    ms = None                   # time quota
132    goals = None                # quota goals
133    goal_tuner = None           # quota goal tuner
134    reset_interval_ms = None    # quota reset interval
135    fail_charge_num = None
136    fail_charge_denom = None
137    weight_sz_permil = None
138    weight_nr_accesses_permil = None
139    weight_age_permil = None
140    scheme = None               # owner scheme
141
142    def __init__(self, sz=0, ms=0, goals=None, goal_tuner='consist',
143                 reset_interval_ms=0, fail_charge_num=0, fail_charge_denom=0,
144                 weight_sz_permil=0, weight_nr_accesses_permil=0,
145                 weight_age_permil=0):
146        self.sz = sz
147        self.ms = ms
148        self.reset_interval_ms = reset_interval_ms
149        self.weight_sz_permil = weight_sz_permil
150        self.weight_nr_accesses_permil = weight_nr_accesses_permil
151        self.weight_age_permil = weight_age_permil
152        self.goals = goals if goals is not None else []
153        self.goal_tuner = goal_tuner
154        for idx, goal in enumerate(self.goals):
155            goal.idx = idx
156            goal.quota = self
157        self.fail_charge_num = fail_charge_num
158        self.fail_charge_denom = fail_charge_denom
159
160    def sysfs_dir(self):
161        return os.path.join(self.scheme.sysfs_dir(), 'quotas')
162
163    def stage(self):
164        err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz)
165        if err is not None:
166            return err
167        err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms)
168        if err is not None:
169            return err
170        err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'),
171                         self.reset_interval_ms)
172        if err is not None:
173            return err
174
175        err = write_file(os.path.join(
176            self.sysfs_dir(), 'weights', 'sz_permil'), self.weight_sz_permil)
177        if err is not None:
178            return err
179        err = write_file(os.path.join(
180            self.sysfs_dir(), 'weights', 'nr_accesses_permil'),
181                         self.weight_nr_accesses_permil)
182        if err is not None:
183            return err
184        err = write_file(os.path.join(
185            self.sysfs_dir(), 'weights', 'age_permil'), self.weight_age_permil)
186        if err is not None:
187            return err
188
189        nr_goals_file = os.path.join(self.sysfs_dir(), 'goals', 'nr_goals')
190        content, err = read_file(nr_goals_file)
191        if err is not None:
192            return err
193        if int(content) != len(self.goals):
194            err = write_file(nr_goals_file, len(self.goals))
195            if err is not None:
196                return err
197        for goal in self.goals:
198            err = goal.stage()
199            if err is not None:
200                return err
201        err = write_file(
202                os.path.join(self.sysfs_dir(), 'goal_tuner'), self.goal_tuner)
203        if err is not None:
204            return err
205
206        err = write_file(
207                os.path.join(self.sysfs_dir(), 'fail_charge_num'),
208                self.fail_charge_num)
209        if err is not None:
210            return err
211        err = write_file(
212                os.path.join(self.sysfs_dir(), 'fail_charge_denom'),
213                self.fail_charge_denom)
214        if err is not None:
215            return err
216
217        return None
218
219class DamosWatermarks:
220    metric = None
221    interval = None
222    high = None
223    mid = None
224    low = None
225    scheme = None   # owner scheme
226
227    def __init__(self, metric='none', interval=0, high=0, mid=0, low=0):
228        self.metric = metric
229        self.interval = interval
230        self.high = high
231        self.mid = mid
232        self.low = low
233
234    def sysfs_dir(self):
235        return os.path.join(self.scheme.sysfs_dir(), 'watermarks')
236
237    def stage(self):
238        err = write_file(os.path.join(self.sysfs_dir(), 'metric'), self.metric)
239        if err is not None:
240            return err
241        err = write_file(os.path.join(self.sysfs_dir(), 'interval_us'),
242                         self.interval)
243        if err is not None:
244            return err
245        err = write_file(os.path.join(self.sysfs_dir(), 'high'), self.high)
246        if err is not None:
247            return err
248        err = write_file(os.path.join(self.sysfs_dir(), 'mid'), self.mid)
249        if err is not None:
250            return err
251        err = write_file(os.path.join(self.sysfs_dir(), 'low'), self.low)
252        if err is not None:
253            return err
254
255class DamosFilter:
256    type_ = None
257    matching = None
258    allow = None
259    memcg_path = None
260    addr_start = None
261    addr_end = None
262    target_idx = None
263    min_ = None
264    max_ = None
265    idx = None
266    filters = None  # owner filters
267
268    def __init__(self, type_='anon', matching=False, allow=False,
269                 memcg_path='', addr_start=0, addr_end=0, target_idx=0, min_=0,
270                 max_=0):
271        self.type_ = type_
272        self.matching = matching
273        self.allow = allow
274        self.memcg_path = memcg_path
275        self.addr_start = addr_start
276        self.addr_end = addr_end
277        self.target_idx = target_idx
278        self.min_ = min_
279        self.max_ = max_
280
281    def sysfs_dir(self):
282        return os.path.join(self.filters.sysfs_dir(), '%d' % self.idx)
283
284    def stage(self):
285        err = write_file(os.path.join(self.sysfs_dir(), 'type'), self.type_)
286        if err is not None:
287            return err
288        err = write_file(os.path.join(self.sysfs_dir(), 'matching'),
289                         self.matching)
290        if err is not None:
291            return err
292        err = write_file(os.path.join(self.sysfs_dir(), 'allow'), self.allow)
293        if err is not None:
294            return err
295        err = write_file(os.path.join(self.sysfs_dir(), 'memcg_path'),
296                         self.memcg_path)
297        if err is not None:
298            return err
299        err = write_file(os.path.join(self.sysfs_dir(), 'addr_start'),
300                         self.addr_start)
301        if err is not None:
302            return err
303        err = write_file(os.path.join(self.sysfs_dir(), 'addr_end'),
304                         self.addr_end)
305        if err is not None:
306            return err
307        err = write_file(os.path.join(self.sysfs_dir(), 'damon_target_idx'),
308                         self.target_idx)
309        if err is not None:
310            return err
311        err = write_file(os.path.join(self.sysfs_dir(), 'min'), self.min_)
312        if err is not None:
313            return err
314        err = write_file(os.path.join(self.sysfs_dir(), 'max'), self.max_)
315        if err is not None:
316            return err
317        return None
318
319class DamosFilters:
320    name = None
321    filters = None
322    scheme = None   # owner scheme
323
324    def __init__(self, name, filters=None):
325        self.name = name
326        if filters is None:
327            filters = []
328        self.filters = filters
329        for idx, filter_ in enumerate(self.filters):
330            filter_.idx = idx
331            filter_.filters = self
332
333    def sysfs_dir(self):
334        return os.path.join(self.scheme.sysfs_dir(), self.name)
335
336    def stage(self):
337        err = write_file(os.path.join(self.sysfs_dir(), 'nr_filters'),
338                         len(self.filters))
339        if err is not None:
340            return err
341        for filter_ in self.filters:
342            err = filter_.stage()
343            if err is not None:
344                return err
345        return None
346
347class DamosDest:
348    id = None
349    weight = None
350    idx = None
351    dests = None    # owner dests
352
353    def __init__(self, id=0, weight=0):
354        self.id = id
355        self.weight = weight
356
357    def sysfs_dir(self):
358        return os.path.join(self.dests.sysfs_dir(), '%d' % self.idx)
359
360    def stage(self):
361        err = write_file(os.path.join(self.sysfs_dir(), 'id'), self.id)
362        if err is not None:
363            return err
364        err = write_file(os.path.join(self.sysfs_dir(), 'weight'), self.weight)
365        if err is not None:
366            return err
367        return None
368
369class DamosDests:
370    dests = None
371    scheme = None   # owner scheme
372
373    def __init__(self, dests=None):
374        if dests is None:
375            dests = []
376        self.dests = dests
377        for idx, dest in enumerate(self.dests):
378            dest.idx = idx
379            dest.dests = self
380
381    def sysfs_dir(self):
382        return os.path.join(self.scheme.sysfs_dir(), 'dests')
383
384    def stage(self):
385        err = write_file(os.path.join(self.sysfs_dir(), 'nr_dests'),
386                         len(self.dests))
387        if err is not None:
388            return err
389        for dest in self.dests:
390            err = dest.stage()
391            if err is not None:
392                return err
393        return None
394
395class DamosStats:
396    nr_tried = None
397    sz_tried = None
398    nr_applied = None
399    sz_applied = None
400    qt_exceeds = None
401
402    def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds):
403        self.nr_tried = nr_tried
404        self.sz_tried = sz_tried
405        self.nr_applied = nr_applied
406        self.sz_applied = sz_applied
407        self.qt_exceeds = qt_exceeds
408
409class DamosTriedRegion:
410    def __init__(self, start, end, nr_accesses, age):
411        self.start = start
412        self.end = end
413        self.nr_accesses = nr_accesses
414        self.age = age
415
416class Damos:
417    action = None
418    access_pattern = None
419    quota = None
420    watermarks = None
421    core_filters = None
422    ops_filters = None
423    filters = None
424    apply_interval_us = None
425    target_nid = None
426    dests = None
427    idx = None
428    context = None
429    tried_bytes = None
430    stats = None
431    tried_regions = None
432
433    def __init__(self, action='stat', access_pattern=None, quota=None,
434                 watermarks=None, core_filters=None, ops_filters=None,
435                 filters=None, target_nid=0, dests=None,
436                 apply_interval_us=0):
437        self.action = action
438        if access_pattern is None:
439            access_pattern = DamosAccessPattern()
440        self.access_pattern = access_pattern
441        self.access_pattern.scheme = self
442        if quota is None:
443            quota = DamosQuota()
444        self.quota = quota
445        self.quota.scheme = self
446        if watermarks is None:
447            watermarks = DamosWatermarks()
448        self.watermarks = watermarks
449        self.watermarks.scheme = self
450
451        self.core_filters = DamosFilters(name='core_filters',
452                                         filters=core_filters)
453        self.core_filters.scheme = self
454        self.ops_filters = DamosFilters(name='ops_filters',
455                                         filters=ops_filters)
456        self.ops_filters.scheme = self
457        self.filters = DamosFilters(name='filters', filters=filters)
458        self.filters.scheme = self
459
460        self.target_nid = target_nid
461        if dests is None:
462            dests = DamosDests()
463        self.dests = dests
464        self.dests.scheme = self
465
466        self.apply_interval_us = apply_interval_us
467
468    def sysfs_dir(self):
469        return os.path.join(
470                self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
471
472    def stage(self):
473        err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
474        if err is not None:
475            return err
476        err = self.access_pattern.stage()
477        if err is not None:
478            return err
479        err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'),
480                         '%d' % self.apply_interval_us)
481        if err is not None:
482            return err
483
484        err = self.quota.stage()
485        if err is not None:
486            return err
487
488        err = self.watermarks.stage()
489        if err is not None:
490            return err
491
492        err = self.core_filters.stage()
493        if err is not None:
494            return err
495        err = self.ops_filters.stage()
496        if err is not None:
497            return err
498        err = self.filters.stage()
499        if err is not None:
500            return err
501
502        err = write_file(os.path.join(self.sysfs_dir(), 'target_nid'), '%d' %
503                         self.target_nid)
504        if err is not None:
505            return err
506
507        err = self.dests.stage()
508        if err is not None:
509            return err
510
511class DamonTarget:
512    pid = None
513    obsolete = None
514    # todo: Support target regions if test is made
515    idx = None
516    context = None
517
518    def __init__(self, pid, obsolete=False):
519        self.pid = pid
520        self.obsolete = obsolete
521
522    def sysfs_dir(self):
523        return os.path.join(
524                self.context.sysfs_dir(), 'targets', '%d' % self.idx)
525
526    def stage(self):
527        err = write_file(
528                os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
529        if err is not None:
530            return err
531        err = write_file(
532                os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
533        if err is not None:
534            return err
535        return write_file(
536                os.path.join(self.sysfs_dir(), 'obsolete_target'),
537                'Y' if self.obsolete else 'N')
538
539class IntervalsGoal:
540    access_bp = None
541    aggrs = None
542    min_sample_us = None
543    max_sample_us = None
544    attrs = None    # owner DamonAttrs
545
546    def __init__(self, access_bp=0, aggrs=0, min_sample_us=0, max_sample_us=0):
547        self.access_bp = access_bp
548        self.aggrs = aggrs
549        self.min_sample_us = min_sample_us
550        self.max_sample_us = max_sample_us
551
552    def sysfs_dir(self):
553        return os.path.join(self.attrs.interval_sysfs_dir(), 'intervals_goal')
554
555    def stage(self):
556        err = write_file(
557                os.path.join(self.sysfs_dir(), 'access_bp'), self.access_bp)
558        if err is not None:
559            return err
560        err = write_file(os.path.join(self.sysfs_dir(), 'aggrs'), self.aggrs)
561        if err is not None:
562            return err
563        err = write_file(os.path.join(self.sysfs_dir(), 'min_sample_us'),
564                         self.min_sample_us)
565        if err is not None:
566            return err
567        err = write_file(os.path.join(self.sysfs_dir(), 'max_sample_us'),
568                         self.max_sample_us)
569        if err is not None:
570            return err
571        return None
572
573class DamonFilter:
574    type_ = None
575    matching = None
576    allow = None
577    filters = None
578    path = None
579    idx = None
580
581    def __init__(self, type_='anon', matching=False, allow=False, path=None):
582        self.type_ = type_
583        self.matching = matching
584        self.allow = allow
585        self.path = path
586
587    def sysfs_dir(self):
588        return os.path.join(self.filters.sysfs_dir(), '%d' % self.idx)
589
590    def stage(self):
591        err = write_file(os.path.join(self.sysfs_dir(), 'type'), self.type_)
592        if err is not None:
593            return err
594        err = write_file(os.path.join(self.sysfs_dir(), 'matching'),
595                         'Y' if self.matching else 'N')
596        if err is not None:
597            return err
598        err = write_file(os.path.join(self.sysfs_dir(), 'allow'),
599                         'Y' if self.allow else 'N')
600        if err is not None:
601            return err
602        if self.type_ == 'memcg':
603            err = write_file(os.path.join(self.sysfs_dir(), 'path'), self.path)
604            if err is not None:
605                return err
606        return None
607
608class DamonFilters:
609    filters = None
610    probe = None
611
612    def __init__(self, filters=None):
613        if filters is None:
614            filters = []
615        self.filters = filters
616        for idx, filter in enumerate(self.filters):
617            filter.filters = self
618            filter.idx = idx
619
620    def sysfs_dir(self):
621        return os.path.join(self.probe.sysfs_dir(), 'filters')
622
623    def stage(self):
624        err = write_file(
625                os.path.join(self.sysfs_dir(), 'nr_filters'),
626                len(self.filters))
627        if err is not None:
628            return err
629        for filter in self.filters:
630            err = filter.stage()
631            if err is not None:
632                return err
633        return None
634
635class DamonProbe:
636    weight = None
637    filters = None
638    probes = None
639    idx = None
640
641    def __init__(self, weight=0, filters=None):
642        self.weight = weight
643        if filters is None:
644            filters = DamonFilters()
645        self.filters = filters
646        self.filters.probe = self
647
648    def sysfs_dir(self):
649        return os.path.join(self.probes.sysfs_dir(), '%d' % self.idx)
650
651    def stage(self):
652        err = write_file(
653                os.path.join(self.sysfs_dir(), 'weight'), '%d' % self.weight)
654        if err is not None:
655            return err
656        return self.filters.stage()
657
658class DamonProbes:
659    probes = None
660    attrs = None
661
662    def __init__(self, probes=None):
663        if probes is None:
664            probes = []
665        self.probes = probes
666        for idx, probe in enumerate(self.probes):
667            probe.probes = self
668            probe.idx = idx
669
670    def sysfs_dir(self):
671        return os.path.join(self.attrs.sysfs_dir(), 'probes')
672
673    def stage(self):
674        err = write_file(os.path.join(self.sysfs_dir(), 'nr_probes'),
675                         len(self.probes))
676        if err is not None:
677            return err
678        for probe in self.probes:
679            err = probe.stage()
680            if err is not None:
681                return err
682        return None
683
684class DamonAttrs:
685    sample_us = None
686    aggr_us = None
687    intervals_goal = None
688    update_us = None
689    min_nr_regions = None
690    max_nr_regions = None
691    probes = None
692    context = None
693
694    def __init__(self, sample_us=5000, aggr_us=100000,
695                 intervals_goal=None, update_us=1000000, min_nr_regions=10,
696                 max_nr_regions=1000, probes=None):
697        self.sample_us = sample_us
698        self.aggr_us = aggr_us
699        if intervals_goal is None:
700            intervals_goal = IntervalsGoal()
701        self.intervals_goal = intervals_goal
702        self.intervals_goal.attrs = self
703        self.update_us = update_us
704        self.min_nr_regions = min_nr_regions
705        self.max_nr_regions = max_nr_regions
706        if probes is None:
707            probes = DamonProbes()
708        self.probes = probes
709        self.probes.attrs = self
710
711    def interval_sysfs_dir(self):
712        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
713                'intervals')
714
715    def nr_regions_range_sysfs_dir(self):
716        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
717                'nr_regions')
718
719    def sysfs_dir(self):
720        return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs')
721
722    def stage(self):
723        err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
724                self.sample_us)
725        if err is not None:
726            return err
727        err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
728                self.aggr_us)
729        if err is not None:
730            return err
731        err = self.intervals_goal.stage()
732        if err is not None:
733            return err
734        err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
735                self.update_us)
736        if err is not None:
737            return err
738
739        err = write_file(
740                os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
741                self.min_nr_regions)
742        if err is not None:
743            return err
744
745        err = write_file(
746                os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
747                self.max_nr_regions)
748        if err is not None:
749            return err
750
751        err = self.probes.stage()
752        if err is not None:
753            return err
754
755class DamonCtx:
756    ops = None
757    monitoring_attrs = None
758    targets = None
759    schemes = None
760    kdamond = None
761    pause = None
762    idx = None
763
764    def __init__(self, ops='paddr', monitoring_attrs=None, targets=None,
765            schemes=None, pause=False):
766        self.ops = ops
767        if monitoring_attrs is None:
768            monitoring_attrs = DamonAttrs()
769        self.monitoring_attrs = monitoring_attrs
770        self.monitoring_attrs.context = self
771
772        if targets is None:
773            targets = []
774        self.targets = targets
775        for idx, target in enumerate(self.targets):
776            target.idx = idx
777            target.context = self
778
779        if schemes is None:
780            schemes = []
781        self.schemes = schemes
782        for idx, scheme in enumerate(self.schemes):
783            scheme.idx = idx
784            scheme.context = self
785
786        self.pause=pause
787
788    def sysfs_dir(self):
789        return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
790                '%d' % self.idx)
791
792    def stage(self):
793        err = write_file(
794                os.path.join(self.sysfs_dir(), 'operations'), self.ops)
795        if err is not None:
796            return err
797        err = self.monitoring_attrs.stage()
798        if err is not None:
799            return err
800
801        nr_targets_file = os.path.join(
802                self.sysfs_dir(), 'targets', 'nr_targets')
803        content, err = read_file(nr_targets_file)
804        if err is not None:
805            return err
806        if int(content) != len(self.targets):
807            err = write_file(nr_targets_file, '%d' % len(self.targets))
808            if err is not None:
809                return err
810        for target in self.targets:
811            err = target.stage()
812            if err is not None:
813                return err
814
815        nr_schemes_file = os.path.join(
816                self.sysfs_dir(), 'schemes', 'nr_schemes')
817        content, err = read_file(nr_schemes_file)
818        if err is not None:
819            return err
820        if int(content) != len(self.schemes):
821            err = write_file(nr_schemes_file, '%d' % len(self.schemes))
822            if err is not None:
823                return err
824        for scheme in self.schemes:
825            err = scheme.stage()
826            if err is not None:
827                return err
828
829        err = write_file(os.path.join(self.sysfs_dir(), 'pause'), self.pause)
830        if err is not None:
831            return err
832
833        return None
834
835class Kdamond:
836    state = None
837    pid = None
838    refresh_ms = None
839    contexts = None
840    idx = None      # index of this kdamond between siblings
841    kdamonds = None # parent
842
843    def __init__(self, contexts=None, refresh_ms=None):
844        if contexts is None:
845            contexts = []
846        self.contexts = contexts
847        self.refresh_ms = refresh_ms
848        for idx, context in enumerate(self.contexts):
849            context.idx = idx
850            context.kdamond = self
851
852    def sysfs_dir(self):
853        return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
854
855    def start(self):
856        nr_contexts_file = os.path.join(self.sysfs_dir(),
857                'contexts', 'nr_contexts')
858        content, err = read_file(nr_contexts_file)
859        if err is not None:
860            return err
861        if int(content) != len(self.contexts):
862            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
863            if err is not None:
864                return err
865
866        for context in self.contexts:
867            err = context.stage()
868            if err is not None:
869                return err
870        if self.refresh_ms is not None:
871            err = write_file(os.path.join(self.sysfs_dir(), 'refresh_ms'),
872                             '%d' % self.refresh_ms)
873            if err is not None:
874                return err
875        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
876        if err is not None:
877            return err
878        self.pid, err = read_file(os.path.join(self.sysfs_dir(), 'pid'))
879        return err
880
881    def stop(self):
882        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off')
883        return err
884
885    def update_schemes_tried_regions(self):
886        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
887                         'update_schemes_tried_regions')
888        if err is not None:
889            return err
890        for context in self.contexts:
891            for scheme in context.schemes:
892                tried_regions = []
893                tried_regions_dir = os.path.join(
894                        scheme.sysfs_dir(), 'tried_regions')
895                region_indices = []
896                for filename in os.listdir(
897                        os.path.join(scheme.sysfs_dir(), 'tried_regions')):
898                    tried_region_dir = os.path.join(tried_regions_dir, filename)
899                    if not os.path.isdir(tried_region_dir):
900                        continue
901                    region_indices.append(int(filename))
902                for region_idx in sorted(region_indices):
903                    tried_region_dir = os.path.join(tried_regions_dir,
904                                                    '%d' % region_idx)
905                    region_values = []
906                    for f in ['start', 'end', 'nr_accesses', 'age']:
907                        content, err = read_file(
908                                os.path.join(tried_region_dir, f))
909                        if err is not None:
910                            return err
911                        region_values.append(int(content))
912                    tried_regions.append(DamosTriedRegion(*region_values))
913                scheme.tried_regions = tried_regions
914
915    def update_schemes_tried_bytes(self):
916        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
917                'update_schemes_tried_bytes')
918        if err is not None:
919            return err
920        for context in self.contexts:
921            for scheme in context.schemes:
922                content, err = read_file(os.path.join(scheme.sysfs_dir(),
923                    'tried_regions', 'total_bytes'))
924                if err is not None:
925                    return err
926                scheme.tried_bytes = int(content)
927
928    def update_schemes_stats(self):
929        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
930                'update_schemes_stats')
931        if err is not None:
932            return err
933        for context in self.contexts:
934            for scheme in context.schemes:
935                stat_values = []
936                for stat in ['nr_tried', 'sz_tried', 'nr_applied',
937                             'sz_applied', 'qt_exceeds']:
938                    content, err = read_file(
939                            os.path.join(scheme.sysfs_dir(), 'stats', stat))
940                    if err is not None:
941                        return err
942                    stat_values.append(int(content))
943                scheme.stats = DamosStats(*stat_values)
944
945    def update_schemes_effective_quotas(self):
946        err = write_file(os.path.join(self.sysfs_dir(), 'state'),
947                         'update_schemes_effective_quotas')
948        if err is not None:
949            return err
950        for context in self.contexts:
951            for scheme in context.schemes:
952                for goal in scheme.quota.goals:
953                    content, err = read_file(
954                            os.path.join(scheme.quota.sysfs_dir(),
955                                         'effective_bytes'))
956                    if err is not None:
957                        return err
958                    goal.effective_bytes = int(content)
959        return None
960
961    def commit(self):
962        nr_contexts_file = os.path.join(self.sysfs_dir(),
963                'contexts', 'nr_contexts')
964        content, err = read_file(nr_contexts_file)
965        if err is not None:
966            return err
967        if int(content) != len(self.contexts):
968            err = write_file(nr_contexts_file, '%d' % len(self.contexts))
969            if err is not None:
970                return err
971
972        for context in self.contexts:
973            err = context.stage()
974            if err is not None:
975                return err
976        err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit')
977        return err
978
979
980    def commit_schemes_quota_goals(self):
981        for context in self.contexts:
982            for scheme in context.schemes:
983                for goal in scheme.quota.goals:
984                    err = goal.stage()
985                    if err is not None:
986                        print('commit_schemes_quota_goals failed staging: %s'%
987                              err)
988                        exit(1)
989        return write_file(os.path.join(self.sysfs_dir(), 'state'),
990                         'commit_schemes_quota_goals')
991
992class Kdamonds:
993    kdamonds = []
994
995    def __init__(self, kdamonds=None):
996        if kdamonds is None:
997            kdamonds = []
998        self.kdamonds = kdamonds
999        for idx, kdamond in enumerate(self.kdamonds):
1000            kdamond.idx = idx
1001            kdamond.kdamonds = self
1002
1003    def sysfs_dir(self):
1004        return os.path.join(sysfs_root, 'kdamonds')
1005
1006    def start(self):
1007        err = write_file(os.path.join(self.sysfs_dir(),  'nr_kdamonds'),
1008                '%s' % len(self.kdamonds))
1009        if err is not None:
1010            return err
1011        for kdamond in self.kdamonds:
1012            err = kdamond.start()
1013            if err is not None:
1014                return err
1015        return None
1016
1017    def stop(self):
1018        for kdamond in self.kdamonds:
1019            err = kdamond.stop()
1020            if err is not None:
1021                return err
1022        return None
1023