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