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