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**64 - 1] 56 if self.age is None: 57 self.age = [0, 2**64 - 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 effective_bytes = None 97 quota = None # owner quota 98 idx = None 99 100 def __init__(self, metric, target_value=10000, current_value=0): 101 self.metric = metric 102 self.target_value = target_value 103 self.current_value = current_value 104 105 def sysfs_dir(self): 106 return os.path.join(self.quota.sysfs_dir(), 'goals', '%d' % self.idx) 107 108 def stage(self): 109 err = write_file(os.path.join(self.sysfs_dir(), 'target_metric'), 110 self.metric) 111 if err is not None: 112 return err 113 err = write_file(os.path.join(self.sysfs_dir(), 'target_value'), 114 self.target_value) 115 if err is not None: 116 return err 117 err = write_file(os.path.join(self.sysfs_dir(), 'current_value'), 118 self.current_value) 119 if err is not None: 120 return err 121 return None 122 123class DamosQuota: 124 sz = None # size quota, in bytes 125 ms = None # time quota 126 goals = None # quota goals 127 reset_interval_ms = None # quota reset interval 128 scheme = None # owner scheme 129 130 def __init__(self, sz=0, ms=0, goals=None, reset_interval_ms=0): 131 self.sz = sz 132 self.ms = ms 133 self.reset_interval_ms = reset_interval_ms 134 self.goals = goals if goals is not None else [] 135 for idx, goal in enumerate(self.goals): 136 goal.idx = idx 137 goal.quota = self 138 139 def sysfs_dir(self): 140 return os.path.join(self.scheme.sysfs_dir(), 'quotas') 141 142 def stage(self): 143 err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz) 144 if err is not None: 145 return err 146 err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms) 147 if err is not None: 148 return err 149 err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'), 150 self.reset_interval_ms) 151 if err is not None: 152 return err 153 154 nr_goals_file = os.path.join(self.sysfs_dir(), 'goals', 'nr_goals') 155 content, err = read_file(nr_goals_file) 156 if err is not None: 157 return err 158 if int(content) != len(self.goals): 159 err = write_file(nr_goals_file, len(self.goals)) 160 if err is not None: 161 return err 162 for goal in self.goals: 163 err = goal.stage() 164 if err is not None: 165 return err 166 return None 167 168class DamosStats: 169 nr_tried = None 170 sz_tried = None 171 nr_applied = None 172 sz_applied = None 173 qt_exceeds = None 174 175 def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds): 176 self.nr_tried = nr_tried 177 self.sz_tried = sz_tried 178 self.nr_applied = nr_applied 179 self.sz_applied = sz_applied 180 self.qt_exceeds = qt_exceeds 181 182class DamosTriedRegion: 183 def __init__(self, start, end, nr_accesses, age): 184 self.start = start 185 self.end = end 186 self.nr_accesses = nr_accesses 187 self.age = age 188 189class Damos: 190 action = None 191 access_pattern = None 192 quota = None 193 apply_interval_us = None 194 # todo: Support watermarks, stats 195 idx = None 196 context = None 197 tried_bytes = None 198 stats = None 199 tried_regions = None 200 201 def __init__(self, action='stat', access_pattern=DamosAccessPattern(), 202 quota=DamosQuota(), apply_interval_us=0): 203 self.action = action 204 self.access_pattern = access_pattern 205 self.access_pattern.scheme = self 206 self.quota = quota 207 self.quota.scheme = self 208 self.apply_interval_us = apply_interval_us 209 210 def sysfs_dir(self): 211 return os.path.join( 212 self.context.sysfs_dir(), 'schemes', '%d' % self.idx) 213 214 def stage(self): 215 err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action) 216 if err is not None: 217 return err 218 err = self.access_pattern.stage() 219 if err is not None: 220 return err 221 err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'), 222 '%d' % self.apply_interval_us) 223 if err is not None: 224 return err 225 226 err = self.quota.stage() 227 if err is not None: 228 return err 229 230 # disable watermarks 231 err = write_file( 232 os.path.join(self.sysfs_dir(), 'watermarks', 'metric'), 'none') 233 if err is not None: 234 return err 235 236 # disable filters 237 err = write_file( 238 os.path.join(self.sysfs_dir(), 'filters', 'nr_filters'), '0') 239 if err is not None: 240 return err 241 242class DamonTarget: 243 pid = None 244 # todo: Support target regions if test is made 245 idx = None 246 context = None 247 248 def __init__(self, pid): 249 self.pid = pid 250 251 def sysfs_dir(self): 252 return os.path.join( 253 self.context.sysfs_dir(), 'targets', '%d' % self.idx) 254 255 def stage(self): 256 err = write_file( 257 os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0') 258 if err is not None: 259 return err 260 return write_file( 261 os.path.join(self.sysfs_dir(), 'pid_target'), self.pid) 262 263class DamonAttrs: 264 sample_us = None 265 aggr_us = None 266 update_us = None 267 min_nr_regions = None 268 max_nr_regions = None 269 context = None 270 271 def __init__(self, sample_us=5000, aggr_us=100000, update_us=1000000, 272 min_nr_regions=10, max_nr_regions=1000): 273 self.sample_us = sample_us 274 self.aggr_us = aggr_us 275 self.update_us = update_us 276 self.min_nr_regions = min_nr_regions 277 self.max_nr_regions = max_nr_regions 278 279 def interval_sysfs_dir(self): 280 return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs', 281 'intervals') 282 283 def nr_regions_range_sysfs_dir(self): 284 return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs', 285 'nr_regions') 286 287 def stage(self): 288 err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'), 289 self.sample_us) 290 if err is not None: 291 return err 292 err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'), 293 self.aggr_us) 294 if err is not None: 295 return err 296 err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'), 297 self.update_us) 298 if err is not None: 299 return err 300 301 err = write_file( 302 os.path.join(self.nr_regions_range_sysfs_dir(), 'min'), 303 self.min_nr_regions) 304 if err is not None: 305 return err 306 307 err = write_file( 308 os.path.join(self.nr_regions_range_sysfs_dir(), 'max'), 309 self.max_nr_regions) 310 if err is not None: 311 return err 312 313class DamonCtx: 314 ops = None 315 monitoring_attrs = None 316 targets = None 317 schemes = None 318 kdamond = None 319 idx = None 320 321 def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[], 322 schemes=[]): 323 self.ops = ops 324 self.monitoring_attrs = monitoring_attrs 325 self.monitoring_attrs.context = self 326 327 self.targets = targets 328 for idx, target in enumerate(self.targets): 329 target.idx = idx 330 target.context = self 331 332 self.schemes = schemes 333 for idx, scheme in enumerate(self.schemes): 334 scheme.idx = idx 335 scheme.context = self 336 337 def sysfs_dir(self): 338 return os.path.join(self.kdamond.sysfs_dir(), 'contexts', 339 '%d' % self.idx) 340 341 def stage(self): 342 err = write_file( 343 os.path.join(self.sysfs_dir(), 'operations'), self.ops) 344 if err is not None: 345 return err 346 err = self.monitoring_attrs.stage() 347 if err is not None: 348 return err 349 350 nr_targets_file = os.path.join( 351 self.sysfs_dir(), 'targets', 'nr_targets') 352 content, err = read_file(nr_targets_file) 353 if err is not None: 354 return err 355 if int(content) != len(self.targets): 356 err = write_file(nr_targets_file, '%d' % len(self.targets)) 357 if err is not None: 358 return err 359 for target in self.targets: 360 err = target.stage() 361 if err is not None: 362 return err 363 364 nr_schemes_file = os.path.join( 365 self.sysfs_dir(), 'schemes', 'nr_schemes') 366 content, err = read_file(nr_schemes_file) 367 if err is not None: 368 return err 369 if int(content) != len(self.schemes): 370 err = write_file(nr_schemes_file, '%d' % len(self.schemes)) 371 if err is not None: 372 return err 373 for scheme in self.schemes: 374 err = scheme.stage() 375 if err is not None: 376 return err 377 return None 378 379class Kdamond: 380 state = None 381 pid = None 382 contexts = None 383 idx = None # index of this kdamond between siblings 384 kdamonds = None # parent 385 386 def __init__(self, contexts=[]): 387 self.contexts = contexts 388 for idx, context in enumerate(self.contexts): 389 context.idx = idx 390 context.kdamond = self 391 392 def sysfs_dir(self): 393 return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx) 394 395 def start(self): 396 nr_contexts_file = os.path.join(self.sysfs_dir(), 397 'contexts', 'nr_contexts') 398 content, err = read_file(nr_contexts_file) 399 if err is not None: 400 return err 401 if int(content) != len(self.contexts): 402 err = write_file(nr_contexts_file, '%d' % len(self.contexts)) 403 if err is not None: 404 return err 405 406 for context in self.contexts: 407 err = context.stage() 408 if err is not None: 409 return err 410 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on') 411 if err is not None: 412 return err 413 self.pid, err = read_file(os.path.join(self.sysfs_dir(), 'pid')) 414 return err 415 416 def stop(self): 417 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off') 418 return err 419 420 def update_schemes_tried_regions(self): 421 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 422 'update_schemes_tried_regions') 423 if err is not None: 424 return err 425 for context in self.contexts: 426 for scheme in context.schemes: 427 tried_regions = [] 428 tried_regions_dir = os.path.join( 429 scheme.sysfs_dir(), 'tried_regions') 430 region_indices = [] 431 for filename in os.listdir( 432 os.path.join(scheme.sysfs_dir(), 'tried_regions')): 433 tried_region_dir = os.path.join(tried_regions_dir, filename) 434 if not os.path.isdir(tried_region_dir): 435 continue 436 region_indices.append(int(filename)) 437 for region_idx in sorted(region_indices): 438 tried_region_dir = os.path.join(tried_regions_dir, 439 '%d' % region_idx) 440 region_values = [] 441 for f in ['start', 'end', 'nr_accesses', 'age']: 442 content, err = read_file( 443 os.path.join(tried_region_dir, f)) 444 if err is not None: 445 return err 446 region_values.append(int(content)) 447 tried_regions.append(DamosTriedRegion(*region_values)) 448 scheme.tried_regions = tried_regions 449 450 def update_schemes_tried_bytes(self): 451 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 452 'update_schemes_tried_bytes') 453 if err is not None: 454 return err 455 for context in self.contexts: 456 for scheme in context.schemes: 457 content, err = read_file(os.path.join(scheme.sysfs_dir(), 458 'tried_regions', 'total_bytes')) 459 if err is not None: 460 return err 461 scheme.tried_bytes = int(content) 462 463 def update_schemes_stats(self): 464 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 465 'update_schemes_stats') 466 if err is not None: 467 return err 468 for context in self.contexts: 469 for scheme in context.schemes: 470 stat_values = [] 471 for stat in ['nr_tried', 'sz_tried', 'nr_applied', 472 'sz_applied', 'qt_exceeds']: 473 content, err = read_file( 474 os.path.join(scheme.sysfs_dir(), 'stats', stat)) 475 if err is not None: 476 return err 477 stat_values.append(int(content)) 478 scheme.stats = DamosStats(*stat_values) 479 480 def update_schemes_effective_quotas(self): 481 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 482 'update_schemes_effective_quotas') 483 if err is not None: 484 return err 485 for context in self.contexts: 486 for scheme in context.schemes: 487 for goal in scheme.quota.goals: 488 content, err = read_file( 489 os.path.join(scheme.quota.sysfs_dir(), 490 'effective_bytes')) 491 if err is not None: 492 return err 493 goal.effective_bytes = int(content) 494 return None 495 496 def commit(self): 497 nr_contexts_file = os.path.join(self.sysfs_dir(), 498 'contexts', 'nr_contexts') 499 content, err = read_file(nr_contexts_file) 500 if err is not None: 501 return err 502 if int(content) != len(self.contexts): 503 err = write_file(nr_contexts_file, '%d' % len(self.contexts)) 504 if err is not None: 505 return err 506 507 for context in self.contexts: 508 err = context.stage() 509 if err is not None: 510 return err 511 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit') 512 return err 513 514 515 def commit_schemes_quota_goals(self): 516 for context in self.contexts: 517 for scheme in context.schemes: 518 for goal in scheme.quota.goals: 519 err = goal.stage() 520 if err is not None: 521 print('commit_schemes_quota_goals failed stagign: %s'% 522 err) 523 exit(1) 524 return write_file(os.path.join(self.sysfs_dir(), 'state'), 525 'commit_schemes_quota_goals') 526 527class Kdamonds: 528 kdamonds = [] 529 530 def __init__(self, kdamonds=[]): 531 self.kdamonds = kdamonds 532 for idx, kdamond in enumerate(self.kdamonds): 533 kdamond.idx = idx 534 kdamond.kdamonds = self 535 536 def sysfs_dir(self): 537 return os.path.join(sysfs_root, 'kdamonds') 538 539 def start(self): 540 err = write_file(os.path.join(self.sysfs_dir(), 'nr_kdamonds'), 541 '%s' % len(self.kdamonds)) 542 if err is not None: 543 return err 544 for kdamond in self.kdamonds: 545 err = kdamond.start() 546 if err is not None: 547 return err 548 return None 549 550 def stop(self): 551 for kdamond in self.kdamonds: 552 err = kdamond.stop() 553 if err is not None: 554 return err 555 return None 556