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