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 return err 412 413 def stop(self): 414 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off') 415 return err 416 417 def update_schemes_tried_regions(self): 418 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 419 'update_schemes_tried_regions') 420 if err is not None: 421 return err 422 for context in self.contexts: 423 for scheme in context.schemes: 424 tried_regions = [] 425 tried_regions_dir = os.path.join( 426 scheme.sysfs_dir(), 'tried_regions') 427 region_indices = [] 428 for filename in os.listdir( 429 os.path.join(scheme.sysfs_dir(), 'tried_regions')): 430 tried_region_dir = os.path.join(tried_regions_dir, filename) 431 if not os.path.isdir(tried_region_dir): 432 continue 433 region_indices.append(int(filename)) 434 for region_idx in sorted(region_indices): 435 tried_region_dir = os.path.join(tried_regions_dir, 436 '%d' % region_idx) 437 region_values = [] 438 for f in ['start', 'end', 'nr_accesses', 'age']: 439 content, err = read_file( 440 os.path.join(tried_region_dir, f)) 441 if err is not None: 442 return err 443 region_values.append(int(content)) 444 tried_regions.append(DamosTriedRegion(*region_values)) 445 scheme.tried_regions = tried_regions 446 447 def update_schemes_tried_bytes(self): 448 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 449 'update_schemes_tried_bytes') 450 if err is not None: 451 return err 452 for context in self.contexts: 453 for scheme in context.schemes: 454 content, err = read_file(os.path.join(scheme.sysfs_dir(), 455 'tried_regions', 'total_bytes')) 456 if err is not None: 457 return err 458 scheme.tried_bytes = int(content) 459 460 def update_schemes_stats(self): 461 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 462 'update_schemes_stats') 463 if err is not None: 464 return err 465 for context in self.contexts: 466 for scheme in context.schemes: 467 stat_values = [] 468 for stat in ['nr_tried', 'sz_tried', 'nr_applied', 469 'sz_applied', 'qt_exceeds']: 470 content, err = read_file( 471 os.path.join(scheme.sysfs_dir(), 'stats', stat)) 472 if err is not None: 473 return err 474 stat_values.append(int(content)) 475 scheme.stats = DamosStats(*stat_values) 476 477 def update_schemes_effective_quotas(self): 478 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 479 'update_schemes_effective_quotas') 480 if err is not None: 481 return err 482 for context in self.contexts: 483 for scheme in context.schemes: 484 for goal in scheme.quota.goals: 485 content, err = read_file( 486 os.path.join(scheme.quota.sysfs_dir(), 487 'effective_bytes')) 488 if err is not None: 489 return err 490 goal.effective_bytes = int(content) 491 return None 492 493 def commit(self): 494 nr_contexts_file = os.path.join(self.sysfs_dir(), 495 'contexts', 'nr_contexts') 496 content, err = read_file(nr_contexts_file) 497 if err is not None: 498 return err 499 if int(content) != len(self.contexts): 500 err = write_file(nr_contexts_file, '%d' % len(self.contexts)) 501 if err is not None: 502 return err 503 504 for context in self.contexts: 505 err = context.stage() 506 if err is not None: 507 return err 508 err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit') 509 return err 510 511 512 def commit_schemes_quota_goals(self): 513 for context in self.contexts: 514 for scheme in context.schemes: 515 for goal in scheme.quota.goals: 516 err = goal.stage() 517 if err is not None: 518 print('commit_schemes_quota_goals failed stagign: %s'% 519 err) 520 exit(1) 521 return write_file(os.path.join(self.sysfs_dir(), 'state'), 522 'commit_schemes_quota_goals') 523 524class Kdamonds: 525 kdamonds = [] 526 527 def __init__(self, kdamonds=[]): 528 self.kdamonds = kdamonds 529 for idx, kdamond in enumerate(self.kdamonds): 530 kdamond.idx = idx 531 kdamond.kdamonds = self 532 533 def sysfs_dir(self): 534 return os.path.join(sysfs_root, 'kdamonds') 535 536 def start(self): 537 err = write_file(os.path.join(self.sysfs_dir(), 'nr_kdamonds'), 538 '%s' % len(self.kdamonds)) 539 if err is not None: 540 return err 541 for kdamond in self.kdamonds: 542 err = kdamond.start() 543 if err is not None: 544 return err 545 return None 546 547 def stop(self): 548 for kdamond in self.kdamonds: 549 err = kdamond.stop() 550 if err is not None: 551 return err 552 return None 553