_damon_sysfs.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. # SPDX-License-Identifier: GPL-2.0
  2. import os
  3. ksft_skip=4
  4. sysfs_root = None
  5. with open('/proc/mounts', 'r') as f:
  6. for line in f:
  7. dev_name, mount_point, dev_fs = line.split()[:3]
  8. if dev_fs == 'sysfs':
  9. sysfs_root = '%s/kernel/mm/damon/admin' % mount_point
  10. break
  11. if sysfs_root is None:
  12. print('Seems sysfs not mounted?')
  13. exit(ksft_skip)
  14. def write_file(path, string):
  15. "Returns error string if failed, or None otherwise"
  16. string = '%s' % string
  17. try:
  18. with open(path, 'w') as f:
  19. f.write(string)
  20. except Exception as e:
  21. return '%s' % e
  22. return None
  23. def read_file(path):
  24. '''Returns the read content and error string. The read content is None if
  25. the reading failed'''
  26. try:
  27. with open(path, 'r') as f:
  28. return f.read(), None
  29. except Exception as e:
  30. return None, '%s' % e
  31. class DamosAccessPattern:
  32. size = None
  33. nr_accesses = None
  34. age = None
  35. scheme = None
  36. def __init__(self, size=None, nr_accesses=None, age=None):
  37. self.size = size
  38. self.nr_accesses = nr_accesses
  39. self.age = age
  40. if self.size is None:
  41. self.size = [0, 2**64 - 1]
  42. if self.nr_accesses is None:
  43. self.nr_accesses = [0, 2**64 - 1]
  44. if self.age is None:
  45. self.age = [0, 2**64 - 1]
  46. def sysfs_dir(self):
  47. return os.path.join(self.scheme.sysfs_dir(), 'access_pattern')
  48. def stage(self):
  49. err = write_file(
  50. os.path.join(self.sysfs_dir(), 'sz', 'min'), self.size[0])
  51. if err is not None:
  52. return err
  53. err = write_file(
  54. os.path.join(self.sysfs_dir(), 'sz', 'max'), self.size[1])
  55. if err is not None:
  56. return err
  57. err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'min'),
  58. self.nr_accesses[0])
  59. if err is not None:
  60. return err
  61. err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'max'),
  62. self.nr_accesses[1])
  63. if err is not None:
  64. return err
  65. err = write_file(
  66. os.path.join(self.sysfs_dir(), 'age', 'min'), self.age[0])
  67. if err is not None:
  68. return err
  69. err = write_file(
  70. os.path.join(self.sysfs_dir(), 'age', 'max'), self.age[1])
  71. if err is not None:
  72. return err
  73. qgoal_metric_user_input = 'user_input'
  74. qgoal_metric_some_mem_psi_us = 'some_mem_psi_us'
  75. qgoal_metrics = [qgoal_metric_user_input, qgoal_metric_some_mem_psi_us]
  76. class DamosQuotaGoal:
  77. metric = None
  78. target_value = None
  79. current_value = None
  80. effective_bytes = None
  81. quota = None # owner quota
  82. idx = None
  83. def __init__(self, metric, target_value=10000, current_value=0):
  84. self.metric = metric
  85. self.target_value = target_value
  86. self.current_value = current_value
  87. def sysfs_dir(self):
  88. return os.path.join(self.quota.sysfs_dir(), 'goals', '%d' % self.idx)
  89. def stage(self):
  90. err = write_file(os.path.join(self.sysfs_dir(), 'target_metric'),
  91. self.metric)
  92. if err is not None:
  93. return err
  94. err = write_file(os.path.join(self.sysfs_dir(), 'target_value'),
  95. self.target_value)
  96. if err is not None:
  97. return err
  98. err = write_file(os.path.join(self.sysfs_dir(), 'current_value'),
  99. self.current_value)
  100. if err is not None:
  101. return err
  102. return None
  103. class DamosQuota:
  104. sz = None # size quota, in bytes
  105. ms = None # time quota
  106. goals = None # quota goals
  107. reset_interval_ms = None # quota reset interval
  108. scheme = None # owner scheme
  109. def __init__(self, sz=0, ms=0, goals=None, reset_interval_ms=0):
  110. self.sz = sz
  111. self.ms = ms
  112. self.reset_interval_ms = reset_interval_ms
  113. self.goals = goals if goals is not None else []
  114. for idx, goal in enumerate(self.goals):
  115. goal.idx = idx
  116. goal.quota = self
  117. def sysfs_dir(self):
  118. return os.path.join(self.scheme.sysfs_dir(), 'quotas')
  119. def stage(self):
  120. err = write_file(os.path.join(self.sysfs_dir(), 'bytes'), self.sz)
  121. if err is not None:
  122. return err
  123. err = write_file(os.path.join(self.sysfs_dir(), 'ms'), self.ms)
  124. if err is not None:
  125. return err
  126. err = write_file(os.path.join(self.sysfs_dir(), 'reset_interval_ms'),
  127. self.reset_interval_ms)
  128. if err is not None:
  129. return err
  130. nr_goals_file = os.path.join(self.sysfs_dir(), 'goals', 'nr_goals')
  131. content, err = read_file(nr_goals_file)
  132. if err is not None:
  133. return err
  134. if int(content) != len(self.goals):
  135. err = write_file(nr_goals_file, len(self.goals))
  136. if err is not None:
  137. return err
  138. for goal in self.goals:
  139. err = goal.stage()
  140. if err is not None:
  141. return err
  142. return None
  143. class DamosStats:
  144. nr_tried = None
  145. sz_tried = None
  146. nr_applied = None
  147. sz_applied = None
  148. qt_exceeds = None
  149. def __init__(self, nr_tried, sz_tried, nr_applied, sz_applied, qt_exceeds):
  150. self.nr_tried = nr_tried
  151. self.sz_tried = sz_tried
  152. self.nr_applied = nr_applied
  153. self.sz_applied = sz_applied
  154. self.qt_exceeds = qt_exceeds
  155. class DamosTriedRegion:
  156. def __init__(self, start, end, nr_accesses, age):
  157. self.start = start
  158. self.end = end
  159. self.nr_accesses = nr_accesses
  160. self.age = age
  161. class Damos:
  162. action = None
  163. access_pattern = None
  164. quota = None
  165. apply_interval_us = None
  166. # todo: Support watermarks, stats
  167. idx = None
  168. context = None
  169. tried_bytes = None
  170. stats = None
  171. tried_regions = None
  172. def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
  173. quota=DamosQuota(), apply_interval_us=0):
  174. self.action = action
  175. self.access_pattern = access_pattern
  176. self.access_pattern.scheme = self
  177. self.quota = quota
  178. self.quota.scheme = self
  179. self.apply_interval_us = apply_interval_us
  180. def sysfs_dir(self):
  181. return os.path.join(
  182. self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
  183. def stage(self):
  184. err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
  185. if err is not None:
  186. return err
  187. err = self.access_pattern.stage()
  188. if err is not None:
  189. return err
  190. err = write_file(os.path.join(self.sysfs_dir(), 'apply_interval_us'),
  191. '%d' % self.apply_interval_us)
  192. if err is not None:
  193. return err
  194. err = self.quota.stage()
  195. if err is not None:
  196. return err
  197. # disable watermarks
  198. err = write_file(
  199. os.path.join(self.sysfs_dir(), 'watermarks', 'metric'), 'none')
  200. if err is not None:
  201. return err
  202. # disable filters
  203. err = write_file(
  204. os.path.join(self.sysfs_dir(), 'filters', 'nr_filters'), '0')
  205. if err is not None:
  206. return err
  207. class DamonTarget:
  208. pid = None
  209. # todo: Support target regions if test is made
  210. idx = None
  211. context = None
  212. def __init__(self, pid):
  213. self.pid = pid
  214. def sysfs_dir(self):
  215. return os.path.join(
  216. self.context.sysfs_dir(), 'targets', '%d' % self.idx)
  217. def stage(self):
  218. err = write_file(
  219. os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
  220. if err is not None:
  221. return err
  222. return write_file(
  223. os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
  224. class DamonAttrs:
  225. sample_us = None
  226. aggr_us = None
  227. update_us = None
  228. min_nr_regions = None
  229. max_nr_regions = None
  230. context = None
  231. def __init__(self, sample_us=5000, aggr_us=100000, update_us=1000000,
  232. min_nr_regions=10, max_nr_regions=1000):
  233. self.sample_us = sample_us
  234. self.aggr_us = aggr_us
  235. self.update_us = update_us
  236. self.min_nr_regions = min_nr_regions
  237. self.max_nr_regions = max_nr_regions
  238. def interval_sysfs_dir(self):
  239. return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
  240. 'intervals')
  241. def nr_regions_range_sysfs_dir(self):
  242. return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
  243. 'nr_regions')
  244. def stage(self):
  245. err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
  246. self.sample_us)
  247. if err is not None:
  248. return err
  249. err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
  250. self.aggr_us)
  251. if err is not None:
  252. return err
  253. err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
  254. self.update_us)
  255. if err is not None:
  256. return err
  257. err = write_file(
  258. os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
  259. self.min_nr_regions)
  260. if err is not None:
  261. return err
  262. err = write_file(
  263. os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
  264. self.max_nr_regions)
  265. if err is not None:
  266. return err
  267. class DamonCtx:
  268. ops = None
  269. monitoring_attrs = None
  270. targets = None
  271. schemes = None
  272. kdamond = None
  273. idx = None
  274. def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
  275. schemes=[]):
  276. self.ops = ops
  277. self.monitoring_attrs = monitoring_attrs
  278. self.monitoring_attrs.context = self
  279. self.targets = targets
  280. for idx, target in enumerate(self.targets):
  281. target.idx = idx
  282. target.context = self
  283. self.schemes = schemes
  284. for idx, scheme in enumerate(self.schemes):
  285. scheme.idx = idx
  286. scheme.context = self
  287. def sysfs_dir(self):
  288. return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
  289. '%d' % self.idx)
  290. def stage(self):
  291. err = write_file(
  292. os.path.join(self.sysfs_dir(), 'operations'), self.ops)
  293. if err is not None:
  294. return err
  295. err = self.monitoring_attrs.stage()
  296. if err is not None:
  297. return err
  298. nr_targets_file = os.path.join(
  299. self.sysfs_dir(), 'targets', 'nr_targets')
  300. content, err = read_file(nr_targets_file)
  301. if err is not None:
  302. return err
  303. if int(content) != len(self.targets):
  304. err = write_file(nr_targets_file, '%d' % len(self.targets))
  305. if err is not None:
  306. return err
  307. for target in self.targets:
  308. err = target.stage()
  309. if err is not None:
  310. return err
  311. nr_schemes_file = os.path.join(
  312. self.sysfs_dir(), 'schemes', 'nr_schemes')
  313. content, err = read_file(nr_schemes_file)
  314. if err is not None:
  315. return err
  316. if int(content) != len(self.schemes):
  317. err = write_file(nr_schemes_file, '%d' % len(self.schemes))
  318. if err is not None:
  319. return err
  320. for scheme in self.schemes:
  321. err = scheme.stage()
  322. if err is not None:
  323. return err
  324. return None
  325. class Kdamond:
  326. state = None
  327. pid = None
  328. contexts = None
  329. idx = None # index of this kdamond between siblings
  330. kdamonds = None # parent
  331. def __init__(self, contexts=[]):
  332. self.contexts = contexts
  333. for idx, context in enumerate(self.contexts):
  334. context.idx = idx
  335. context.kdamond = self
  336. def sysfs_dir(self):
  337. return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
  338. def start(self):
  339. nr_contexts_file = os.path.join(self.sysfs_dir(),
  340. 'contexts', 'nr_contexts')
  341. content, err = read_file(nr_contexts_file)
  342. if err is not None:
  343. return err
  344. if int(content) != len(self.contexts):
  345. err = write_file(nr_contexts_file, '%d' % len(self.contexts))
  346. if err is not None:
  347. return err
  348. for context in self.contexts:
  349. err = context.stage()
  350. if err is not None:
  351. return err
  352. err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
  353. return err
  354. def stop(self):
  355. err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'off')
  356. return err
  357. def update_schemes_tried_regions(self):
  358. err = write_file(os.path.join(self.sysfs_dir(), 'state'),
  359. 'update_schemes_tried_regions')
  360. if err is not None:
  361. return err
  362. for context in self.contexts:
  363. for scheme in context.schemes:
  364. tried_regions = []
  365. tried_regions_dir = os.path.join(
  366. scheme.sysfs_dir(), 'tried_regions')
  367. for filename in os.listdir(
  368. os.path.join(scheme.sysfs_dir(), 'tried_regions')):
  369. tried_region_dir = os.path.join(tried_regions_dir, filename)
  370. if not os.path.isdir(tried_region_dir):
  371. continue
  372. region_values = []
  373. for f in ['start', 'end', 'nr_accesses', 'age']:
  374. content, err = read_file(
  375. os.path.join(tried_region_dir, f))
  376. if err is not None:
  377. return err
  378. region_values.append(int(content))
  379. tried_regions.append(DamosTriedRegion(*region_values))
  380. scheme.tried_regions = tried_regions
  381. def update_schemes_tried_bytes(self):
  382. err = write_file(os.path.join(self.sysfs_dir(), 'state'),
  383. 'update_schemes_tried_bytes')
  384. if err is not None:
  385. return err
  386. for context in self.contexts:
  387. for scheme in context.schemes:
  388. content, err = read_file(os.path.join(scheme.sysfs_dir(),
  389. 'tried_regions', 'total_bytes'))
  390. if err is not None:
  391. return err
  392. scheme.tried_bytes = int(content)
  393. def update_schemes_stats(self):
  394. err = write_file(os.path.join(self.sysfs_dir(), 'state'),
  395. 'update_schemes_stats')
  396. if err is not None:
  397. return err
  398. for context in self.contexts:
  399. for scheme in context.schemes:
  400. stat_values = []
  401. for stat in ['nr_tried', 'sz_tried', 'nr_applied',
  402. 'sz_applied', 'qt_exceeds']:
  403. content, err = read_file(
  404. os.path.join(scheme.sysfs_dir(), 'stats', stat))
  405. if err is not None:
  406. return err
  407. stat_values.append(int(content))
  408. scheme.stats = DamosStats(*stat_values)
  409. def update_schemes_effective_quotas(self):
  410. err = write_file(os.path.join(self.sysfs_dir(), 'state'),
  411. 'update_schemes_effective_quotas')
  412. if err is not None:
  413. return err
  414. for context in self.contexts:
  415. for scheme in context.schemes:
  416. for goal in scheme.quota.goals:
  417. content, err = read_file(
  418. os.path.join(scheme.quota.sysfs_dir(),
  419. 'effective_bytes'))
  420. if err is not None:
  421. return err
  422. goal.effective_bytes = int(content)
  423. return None
  424. def commit(self):
  425. nr_contexts_file = os.path.join(self.sysfs_dir(),
  426. 'contexts', 'nr_contexts')
  427. content, err = read_file(nr_contexts_file)
  428. if err is not None:
  429. return err
  430. if int(content) != len(self.contexts):
  431. err = write_file(nr_contexts_file, '%d' % len(self.contexts))
  432. if err is not None:
  433. return err
  434. for context in self.contexts:
  435. err = context.stage()
  436. if err is not None:
  437. return err
  438. err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'commit')
  439. return err
  440. def commit_schemes_quota_goals(self):
  441. for context in self.contexts:
  442. for scheme in context.schemes:
  443. for goal in scheme.quota.goals:
  444. err = goal.stage()
  445. if err is not None:
  446. print('commit_schemes_quota_goals failed stagign: %s'%
  447. err)
  448. exit(1)
  449. return write_file(os.path.join(self.sysfs_dir(), 'state'),
  450. 'commit_schemes_quota_goals')
  451. class Kdamonds:
  452. kdamonds = []
  453. def __init__(self, kdamonds=[]):
  454. self.kdamonds = kdamonds
  455. for idx, kdamond in enumerate(self.kdamonds):
  456. kdamond.idx = idx
  457. kdamond.kdamonds = self
  458. def sysfs_dir(self):
  459. return os.path.join(sysfs_root, 'kdamonds')
  460. def start(self):
  461. err = write_file(os.path.join(self.sysfs_dir(), 'nr_kdamonds'),
  462. '%s' % len(self.kdamonds))
  463. if err is not None:
  464. return err
  465. for kdamond in self.kdamonds:
  466. err = kdamond.start()
  467. if err is not None:
  468. return err
  469. return None
  470. def stop(self):
  471. for kdamond in self.kdamonds:
  472. err = kdamond.stop()
  473. if err is not None:
  474. return err
  475. return None