damo 3.2.2__tar.gz → 3.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {damo-3.2.2 → damo-3.2.4}/PKG-INFO +5 -5
  2. {damo-3.2.2 → damo-3.2.4}/README.md +4 -4
  3. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon.py +136 -24
  4. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon_args.py +120 -4
  5. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon_features.py +14 -3
  6. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon_sysfs.py +109 -8
  7. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_access.py +5 -0
  8. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_version.py +1 -1
  9. {damo-3.2.2 → damo-3.2.4}/src/damo.egg-info/PKG-INFO +5 -5
  10. {damo-3.2.2 → damo-3.2.4}/pyproject.toml +0 -0
  11. {damo-3.2.2 → damo-3.2.4}/setup.cfg +0 -0
  12. {damo-3.2.2 → damo-3.2.4}/setup.py +0 -0
  13. {damo-3.2.2 → damo-3.2.4}/src/damo/__init__.py +0 -0
  14. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_ascii_color.py +0 -0
  15. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_deprecated.py +0 -0
  16. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_deprecation_notice.py +0 -0
  17. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_dist.py +0 -0
  18. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_fmt_str.py +0 -0
  19. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_fs.py +0 -0
  20. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_print.py +0 -0
  21. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_records.py +0 -0
  22. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_subcmds.py +0 -0
  23. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_subproc.py +0 -0
  24. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_sysinfo.py +0 -0
  25. {damo-3.2.2 → damo-3.2.4}/src/damo/_damo_yaml.py +0 -0
  26. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon_dbgfs.py +0 -0
  27. {damo-3.2.2 → damo-3.2.4}/src/damo/_damon_modules.py +0 -0
  28. {damo-3.2.2 → damo-3.2.4}/src/damo/damo.py +0 -0
  29. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_adjust.py +0 -0
  30. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_args.py +0 -0
  31. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_args_accesses_filter.py +0 -0
  32. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_args_accesses_format.py +0 -0
  33. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_args_damon.py +0 -0
  34. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_convert_record_format.py +0 -0
  35. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_diagnose.py +0 -0
  36. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_features.py +0 -0
  37. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_help.py +0 -0
  38. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_help_access_filter_options.py +0 -0
  39. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_help_access_format_options.py +0 -0
  40. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_help_damon_param_options.py +0 -0
  41. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_lru_sort.py +0 -0
  42. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_module.py +0 -0
  43. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_module_general.py +0 -0
  44. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_module_stat.py +0 -0
  45. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_monitor.py +0 -0
  46. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_nr_regions.py +0 -0
  47. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_pa_layout.py +0 -0
  48. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_reclaim.py +0 -0
  49. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_record.py +0 -0
  50. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_record_info.py +0 -0
  51. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_replay.py +0 -0
  52. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report.py +0 -0
  53. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_damon.py +0 -0
  54. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_footprint.py +0 -0
  55. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_heatmap.py +0 -0
  56. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_holistic.py +0 -0
  57. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_pa_layout.py +0 -0
  58. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_profile.py +0 -0
  59. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_record_info.py +0 -0
  60. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_sysinfo.py +0 -0
  61. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_times.py +0 -0
  62. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_report_trace.py +0 -0
  63. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_schemes.py +0 -0
  64. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_setup_cli_completion.py +0 -0
  65. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_start.py +0 -0
  66. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_stop.py +0 -0
  67. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_tune.py +0 -0
  68. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_validate.py +0 -0
  69. {damo-3.2.2 → damo-3.2.4}/src/damo/damo_wss.py +0 -0
  70. {damo-3.2.2 → damo-3.2.4}/src/damo.egg-info/SOURCES.txt +0 -0
  71. {damo-3.2.2 → damo-3.2.4}/src/damo.egg-info/dependency_links.txt +0 -0
  72. {damo-3.2.2 → damo-3.2.4}/src/damo.egg-info/entry_points.txt +0 -0
  73. {damo-3.2.2 → damo-3.2.4}/src/damo.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: damo
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: DAMON user-space tool
5
5
  Home-page: https://github.com/damonitor/damo
6
6
  Author: SeongJae Park
@@ -71,7 +71,7 @@ The second and last commands will show the access pattern of your workload,
71
71
  like below:
72
72
 
73
73
  ![masim_stairs_snapshot](images/masim_stairs_snapshot.png)
74
- ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.2/images/masim_stairs_heatmap_ascii.png)
74
+ ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.4/images/masim_stairs_heatmap_ascii.png)
75
75
 
76
76
 
77
77
  FAQs
@@ -90,7 +90,7 @@ Where can I get more detailed usage?
90
90
  ------------------------------------
91
91
 
92
92
  The below sections provide quick introductions for `damo`'s major features.
93
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
93
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
94
94
 
95
95
 
96
96
  What does the version numbers mean?
@@ -115,7 +115,7 @@ We try our best to make `damo` stable and doesn't introduce regressions to
115
115
  users. However, nothing goes forever. Sometimes, some features will be
116
116
  deprecated. Some features will have longer support more than others.
117
117
 
118
- In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) and not explicitly
118
+ In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) and not explicitly
119
119
  marked as experimental will be better supported, and provides at least three
120
120
  months of deprecation grace period. Within the grace period, users can ask
121
121
  extension of the support. Even after the grace period, please reach out to
@@ -138,7 +138,7 @@ Quick Intro for Major Features
138
138
  ==============================
139
139
 
140
140
  Below are quick introductions for `damo`'s major features.
141
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
141
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
142
142
 
143
143
 
144
144
  Snapshot Data Access Pattern
@@ -47,7 +47,7 @@ The second and last commands will show the access pattern of your workload,
47
47
  like below:
48
48
 
49
49
  ![masim_stairs_snapshot](images/masim_stairs_snapshot.png)
50
- ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.2/images/masim_stairs_heatmap_ascii.png)
50
+ ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.4/images/masim_stairs_heatmap_ascii.png)
51
51
 
52
52
 
53
53
  FAQs
@@ -66,7 +66,7 @@ Where can I get more detailed usage?
66
66
  ------------------------------------
67
67
 
68
68
  The below sections provide quick introductions for `damo`'s major features.
69
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
69
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
70
70
 
71
71
 
72
72
  What does the version numbers mean?
@@ -91,7 +91,7 @@ We try our best to make `damo` stable and doesn't introduce regressions to
91
91
  users. However, nothing goes forever. Sometimes, some features will be
92
92
  deprecated. Some features will have longer support more than others.
93
93
 
94
- In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) and not explicitly
94
+ In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) and not explicitly
95
95
  marked as experimental will be better supported, and provides at least three
96
96
  months of deprecation grace period. Within the grace period, users can ask
97
97
  extension of the support. Even after the grace period, please reach out to
@@ -114,7 +114,7 @@ Quick Intro for Major Features
114
114
  ==============================
115
115
 
116
116
  Below are quick introductions for `damo`'s major features.
117
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
117
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
118
118
 
119
119
 
120
120
  Snapshot Data Access Pattern
@@ -16,6 +16,84 @@ import _damo_fmt_str
16
16
 
17
17
  # Core data structures
18
18
 
19
+ class DamonFilter:
20
+ filter_type = None # anon, memcg
21
+ matching = None
22
+ allow = None
23
+ path = None
24
+
25
+ def __init__(self, filter_type, matching, allow=False, path=None):
26
+ self.filter_type = filter_type
27
+ self.matching = _damo_fmt_str.text_to_bool(matching)
28
+ self.allow = _damo_fmt_str.text_to_bool(allow)
29
+ self.path = path
30
+
31
+ def to_str(self, raw):
32
+ words = []
33
+ if self.allow:
34
+ words.append('allow')
35
+ else:
36
+ words.append('reject')
37
+ if self.matching is False:
38
+ words.append('non')
39
+ words.append(self.filter_type)
40
+ if self.filter_type in ['anon']:
41
+ return ' '.join(words)
42
+ if self.filter_type == 'memcg':
43
+ return ' '.join(words + [self.path])
44
+
45
+ def __str__(self):
46
+ return self.to_str(False)
47
+
48
+ def __eq__(self, other):
49
+ return type(self) == type(other) and \
50
+ self.filter_type == other.filter_type and \
51
+ self.matching == other.matching and \
52
+ self.allow == other.allow and self.path == other.path
53
+
54
+ @classmethod
55
+ def from_kvpairs(cls, kv):
56
+ return DamonFilter(
57
+ filter_type=kv['filter_type'], matching=kv['matching'],
58
+ allow=kv['allow'], path=kv['path'])
59
+
60
+ def to_kvpairs(self, raw=False):
61
+ return collections.OrderedDict([
62
+ ('filter_type', self.filter_type),
63
+ ('matching', self.matching),
64
+ ('allow', self.allow),
65
+ ('path', self.path),
66
+ ])
67
+
68
+ class DamonProbe:
69
+ filters = None
70
+
71
+ def __init__(self, filters):
72
+ if type(filters) is not list:
73
+ raise Exception('filters for DamonProbe() is not a list')
74
+ self.filters = filters
75
+
76
+ def to_str(self, raw):
77
+ return ', '.join([f.to_str(raw) for f in self.filters])
78
+
79
+ def __str__(self):
80
+ return self.to_str(False)
81
+
82
+ def __eq__(self, other):
83
+ return type(self) == type(other) and \
84
+ self.filters == other.filters
85
+
86
+ @classmethod
87
+ def from_kvpairs(cls, kv):
88
+ return DamonProbe(filters=[
89
+ DamonFilter.from_kvpairs(filter_kv)
90
+ for filter_kv in kv['filters']])
91
+
92
+ def to_kvpairs(self, raw=False):
93
+ return collections.OrderedDict([
94
+ ('filters', [f.to_kvpairs(raw) for f in self.filters]),
95
+ ])
96
+
19
97
  class OpsAttrs:
20
98
  use_reports = None
21
99
  write_only = None
@@ -449,12 +527,13 @@ class DamonRegion:
449
527
  end = None
450
528
  # nr_accesses and age could be None
451
529
  nr_accesses = None
530
+ probe_hits = None # list of integers
452
531
  age = None
453
532
  sz_filter_passed = None
454
533
  scheme = None # non-None if tried region
455
534
 
456
535
  def __init__(self, start, end, nr_accesses=None, nr_accesses_unit=None,
457
- age=None, age_unit=None, sz_filter_passed=0):
536
+ age=None, age_unit=None, sz_filter_passed=0, probe_hits=None):
458
537
  self.start = _damo_fmt_str.text_to_bytes(start)
459
538
  self.end = _damo_fmt_str.text_to_bytes(end)
460
539
 
@@ -463,6 +542,9 @@ class DamonRegion:
463
542
  self.nr_accesses = DamonNrAccesses(nr_accesses, nr_accesses_unit)
464
543
  self.age = DamonAge(age, age_unit)
465
544
  self.sz_filter_passed = sz_filter_passed
545
+ if probe_hits is None:
546
+ probe_hits = []
547
+ self.probe_hits = probe_hits
466
548
 
467
549
  def to_str(self, raw, intervals=None):
468
550
  if self.nr_accesses == None:
@@ -482,6 +564,9 @@ class DamonRegion:
482
564
  _damo_fmt_str.format_addr_range(self.start, self.end, raw),
483
565
  self.nr_accesses.to_str(nr_accesses_unit, raw),
484
566
  self.age.to_str(age_unit, raw))
567
+ if self.probe_hits:
568
+ str += ', probe_hits: %s' % ' '.join(
569
+ ['%d' % h for h in self.probe_hits])
485
570
  if self.sz_filter_passed is not None:
486
571
  str += ', filter_passed: %s' % _damo_fmt_str.format_sz(
487
572
  self.sz_filter_passed, raw)
@@ -505,6 +590,7 @@ class DamonRegion:
505
590
  region = DamonRegion(kvpairs['start'], kvpairs['end'])
506
591
  region.nr_accesses = DamonNrAccesses.from_kvpairs(
507
592
  kvpairs['nr_accesses'])
593
+ region.probe_hits = kvpairs.get('probe_hits', [])
508
594
  region.age = DamonAge.from_kvpairs(kvpairs['age'])
509
595
  if 'sz_filter_passed' in kvpairs:
510
596
  region.sz_filter_passed = _damo_fmt_str.text_to_bytes(
@@ -523,6 +609,7 @@ class DamonRegion:
523
609
  ('end', _damo_fmt_str.format_nr(self.end, raw)),
524
610
  ('nr_accesses', self.nr_accesses.to_kvpairs(raw)),
525
611
  ('age', self.age.to_kvpairs(raw)),
612
+ ('probe_hits', self.probe_hits),
526
613
  ('sz_filter_passed', _damo_fmt_str.format_sz(
527
614
  self.sz_filter_passed, raw)),
528
615
  ])
@@ -724,10 +811,13 @@ qgoal_node_memcg_used_bp = 'node_memcg_used_bp'
724
811
  qgoal_node_memcg_free_bp = 'node_memcg_free_bp'
725
812
  qgoal_active_mem_bp = 'active_mem_bp'
726
813
  qgoal_inactive_mem_bp = 'inactive_mem_bp'
814
+ qgoal_node_eligible_mem_bp = 'node_eligible_mem_bp'
727
815
  qgoal_metrics = [qgoal_user_input, qgoal_some_mem_psi_us,
728
816
  qgoal_node_mem_used_bp, qgoal_node_mem_free_bp,
729
817
  qgoal_node_memcg_used_bp, qgoal_node_memcg_free_bp,
730
- qgoal_active_mem_bp, qgoal_inactive_mem_bp]
818
+ qgoal_active_mem_bp, qgoal_inactive_mem_bp,
819
+ qgoal_node_eligible_mem_bp,
820
+ ]
731
821
 
732
822
  class DamosQuotaGoal:
733
823
  metric = None
@@ -1193,30 +1283,33 @@ class DamosStats:
1193
1283
  max_nr_snapshots=max_nr_snapshots)
1194
1284
 
1195
1285
  # TODO: check support of pageout and lru_(de)prio
1286
+
1287
+ damos_action_willneed = 'willneed'
1288
+ damos_action_cold = 'cold'
1289
+ damos_action_pageout = 'pageout'
1290
+ damos_action_hugepage = 'hugepage'
1291
+ damos_action_collapse = 'collapse'
1292
+ damos_action_nohugepage = 'nohugepage'
1293
+ damos_action_lru_prio = 'lru_prio'
1294
+ damos_action_lru_deprio = 'lru_deprio'
1295
+ damos_action_migrate_hot = 'migrate_hot'
1296
+ damos_action_migrate_cold = 'migrate_cold'
1297
+ damos_action_stat = 'stat'
1298
+
1196
1299
  damos_actions = [
1197
- 'willneed',
1198
- 'cold',
1199
- 'pageout',
1200
- 'hugepage',
1201
- 'nohugepage',
1202
- 'lru_prio',
1203
- 'lru_deprio',
1204
- 'migrate_hot',
1205
- 'migrate_cold',
1206
- 'stat',
1300
+ damos_action_willneed,
1301
+ damos_action_cold,
1302
+ damos_action_pageout,
1303
+ damos_action_hugepage,
1304
+ damos_action_collapse,
1305
+ damos_action_nohugepage,
1306
+ damos_action_lru_prio,
1307
+ damos_action_lru_deprio,
1308
+ damos_action_migrate_hot,
1309
+ damos_action_migrate_cold,
1310
+ damos_action_stat,
1207
1311
  ]
1208
1312
 
1209
- damos_action_willneed = damos_actions[0]
1210
- damos_action_cold = damos_actions[1]
1211
- damos_action_pageout = damos_actions[2]
1212
- damos_action_hugepage = damos_actions[3]
1213
- damos_action_nohugepage = damos_actions[4]
1214
- damos_action_lru_prio = damos_actions[5]
1215
- damos_action_lru_deprio = damos_actions[6]
1216
- damos_action_migrate_hot = damos_actions[7]
1217
- damos_action_migrate_cold = damos_actions[8]
1218
- damos_action_stat = damos_actions[9]
1219
-
1220
1313
  def is_damos_migrate_action(action):
1221
1314
  if action == damos_action_migrate_hot or \
1222
1315
  action == damos_action_migrate_cold:
@@ -1393,6 +1486,7 @@ class DamonCtx:
1393
1486
  targets = None
1394
1487
  intervals = None
1395
1488
  nr_regions = None
1489
+ probes = None
1396
1490
  sample_control = None
1397
1491
  pause = None
1398
1492
  schemes = None
@@ -1400,7 +1494,7 @@ class DamonCtx:
1400
1494
 
1401
1495
  def __init__(self, ops='paddr', targets=None, intervals=None,
1402
1496
  nr_regions=None, schemes=None, ops_attrs=None,
1403
- sample_control=None, addr_unit=None, pause=None):
1497
+ sample_control=None, addr_unit=None, pause=None, probes=None):
1404
1498
  self.ops = ops
1405
1499
  self.ops_attrs = ops_attrs if ops_attrs is not None else OpsAttrs()
1406
1500
  if addr_unit is None:
@@ -1414,6 +1508,9 @@ class DamonCtx:
1414
1508
  if intervals is not None else DamonIntervals())
1415
1509
  self.nr_regions = (nr_regions if nr_regions is not None
1416
1510
  else DamonNrRegionsRange())
1511
+ if probes is None:
1512
+ probes = []
1513
+ self.probes = probes
1417
1514
  if sample_control is None:
1418
1515
  sample_control = DamonSampleControl()
1419
1516
  self.sample_control = sample_control
@@ -1424,6 +1521,16 @@ class DamonCtx:
1424
1521
  for scheme in self.schemes:
1425
1522
  scheme.context = self
1426
1523
 
1524
+ def add_probes_str(self, lines, raw):
1525
+ if len(self.probes) == 0:
1526
+ return
1527
+ if len(self.probes) == 1:
1528
+ lines.append('probe %s' % self.probes[0].to_str(raw))
1529
+ return
1530
+ lines.append('probes')
1531
+ for idx, probe in enumerate(self.probes):
1532
+ lines.append('- %s' % probe.to_str(raw))
1533
+
1427
1534
  def to_str(self, raw, params_only=False, omit_damos_tried_regions=False):
1428
1535
  ops_line_tokens = ['ops: %s' % self.ops]
1429
1536
  if self.ops_attrs.use_reports:
@@ -1437,6 +1544,7 @@ class DamonCtx:
1437
1544
  'addr_unit %s' % _damo_fmt_str.format_sz_accurate(
1438
1545
  self.addr_unit, raw))
1439
1546
  lines = [' '.join(ops_line_tokens)]
1547
+ self.add_probes_str(lines, raw)
1440
1548
  if self.pause is True:
1441
1549
  lines.append('pause: True')
1442
1550
  for idx, target in enumerate(self.targets):
@@ -1476,6 +1584,8 @@ class DamonCtx:
1476
1584
  sample_control = DamonSampleControl()
1477
1585
  addr_unit = kv.get('addr_unit', 1)
1478
1586
  pause = _damo_fmt_str.text_to_bool(kv.get('pause', False))
1587
+ probes = [DamonProbe.from_kvpairs(probe_kv)
1588
+ for probe_kv in kv.get('probes', [])]
1479
1589
  ctx = DamonCtx(
1480
1590
  kv['ops'],
1481
1591
  [DamonTarget.from_kvpairs(t) for t in kv['targets']],
@@ -1485,6 +1595,7 @@ class DamonCtx:
1485
1595
  if 'nr_regions' in kv else DamonNrRegionsRange(),
1486
1596
  [Damos.from_kvpairs(s) for s in kv['schemes']]
1487
1597
  if 'schemes' in kv else [],
1598
+ probes=probes,
1488
1599
  sample_control=sample_control,
1489
1600
  addr_unit=addr_unit,
1490
1601
  pause=pause)
@@ -1499,6 +1610,7 @@ class DamonCtx:
1499
1610
  kv['intervals'] = self.intervals.to_kvpairs(raw)
1500
1611
  if not omit_defaults or self.nr_regions != DamonNrRegionsRange():
1501
1612
  kv['nr_regions'] = self.nr_regions.to_kvpairs(raw)
1613
+ kv['probes'] = [probe.to_kvpairs(raw) for probe in self.probes]
1502
1614
  kv['sample_control'] = self.sample_control.to_kvpairs(raw)
1503
1615
  kv['pause'] = self.pause
1504
1616
  kv['schemes'] = [s.to_kvpairs(raw, omit_defaults, params_only)
@@ -641,8 +641,8 @@ def damon_ctx_for(args, idx):
641
641
 
642
642
  def get_nr_ctxs(args):
643
643
  candidates = []
644
- for v in [args.ops, args.sample, args.aggr, args.updr, args.minr,
645
- args.maxr, args.monitoring_intervals,
644
+ for v in [args.ops, args.nr_probes, args.sample, args.aggr, args.updr,
645
+ args.minr, args.maxr, args.monitoring_intervals,
646
646
  args.monitoring_intervals_goal,
647
647
  args.monitoring_nr_regions_range]:
648
648
  if v is not None:
@@ -752,6 +752,90 @@ def gen_assign_schemes(ctxs, args):
752
752
  scheme_idx += nr
753
753
  return None
754
754
 
755
+ def probe_filter_for(filter_arg_fields):
756
+ fields = filter_arg_fields
757
+ if len(fields) < 2:
758
+ return None, 'expected >=2 fields but %d' % len(fields)
759
+ if not fields[0] in ['allow', 'reject']:
760
+ return None, 'expected allow|reject but %s' % fields[0]
761
+ allow = fields[0] == 'allow'
762
+ fields = fields[1:]
763
+ matching = True
764
+ if fields[0] == 'non':
765
+ matching = False
766
+ fields = fields[1:]
767
+ if len(fields) < 1:
768
+ return None, 'filter type is not given'
769
+ filter_type = fields[0]
770
+ fields = fields[1:]
771
+ path = None
772
+ if filter_type == 'memcg':
773
+ if len(fields) < 1:
774
+ return None, 'memcg path is not given'
775
+ path = fields[0]
776
+ try:
777
+ filter = _damon.DamonFilter(filter_type=filter_type, matching=matching,
778
+ allow=allow, path=path)
779
+ except Exception as e:
780
+ return None, 'filter creation fail (%s)' % e
781
+ return filter, None
782
+
783
+ def probe_filters_for(filters_args):
784
+ filters = []
785
+ for filter_args in filters_args:
786
+ filter, err = probe_filter_for(filter_args)
787
+ if err is not None:
788
+ return None, err
789
+ filters.append(filter)
790
+ return filters, None
791
+
792
+ def probes_for(args):
793
+ filters, err = probe_filters_for(args.probe_filter)
794
+ if err is not None:
795
+ return None, err
796
+ if len(filters) == 0:
797
+ return [], None
798
+ probes = []
799
+ filter_idx = 0
800
+ if args.nr_probe_filters is None:
801
+ args.nr_probe_filters = [len(args.probe_filter)]
802
+ if sum(args.nr_probe_filters) != len(filters):
803
+ return None, \
804
+ '--nr_probe_filters mismatches --probe_filter (%d != %d)' % (
805
+ sum(args.nr_probe_filters), len(filters))
806
+ for nr in args.nr_probe_filters:
807
+ filters_for_probe = filters[filter_idx:filter_idx + nr]
808
+ try:
809
+ probe = _damon.DamonProbe(filters=filters_for_probe)
810
+ except Exception as e:
811
+ return None, 'probe creation fail (%s)' % e
812
+ probes.append(probe)
813
+ filter_idx += nr
814
+ return probes, None
815
+
816
+ def gen_assign_probes(ctxs, args):
817
+ probes, err = probes_for(args)
818
+ if err is not None:
819
+ return err
820
+ if args.nr_probes is None:
821
+ if len(ctxs) != 1 and len(probes) > 0:
822
+ return '--nr_probes is required'
823
+ args.nr_probes = [len(probes)]
824
+ args.nr_probes += [0] * (len(ctxs) - 1)
825
+ if sum(args.nr_probes) != len(probes):
826
+ return '--nr_probes mismatches number of probes (%d != %d)' % (
827
+ sum(args.nr_probes), len(probes))
828
+ if len(args.nr_probes) != len(ctxs):
829
+ return '--nr_probes mismatches number of contexts (%d != %d)' % (
830
+ len(args.nr_probes), len(ctxs))
831
+ ctx_idx = 0
832
+ probe_idx = 0
833
+ for nr in args.nr_probes:
834
+ ctxs[ctx_idx].probes = probes[probe_idx:probe_idx + nr]
835
+ ctx_idx += 1
836
+ probe_idx += nr
837
+ return None
838
+
755
839
  def damon_ctxs_for(args):
756
840
  fillup_none_ctx_args(args)
757
841
  fillup_none_target_args(args)
@@ -770,6 +854,10 @@ def damon_ctxs_for(args):
770
854
  if err is not None:
771
855
  return None, err
772
856
 
857
+ err = gen_assign_probes(ctxs, args)
858
+ if err is not None:
859
+ return None, err
860
+
773
861
  return ctxs, err
774
862
 
775
863
  def kdamonds_from_json_arg(arg):
@@ -854,7 +942,7 @@ def deduce_target_update_args(args):
854
942
  def warn_for(arg, feature):
855
943
  if _damo_sysinfo.damon_feature_available(feature):
856
944
  return
857
- sys.stderr.write('%s is passed but %s DAMON feature is not available' %
945
+ sys.stderr.write('%s is passed but %s DAMON feature is not available\n' %
858
946
  (arg, feature))
859
947
 
860
948
  def warn_for_features(arg, features):
@@ -862,7 +950,7 @@ def warn_for_features(arg, features):
862
950
  if _damo_sysinfo.damon_feature_available(feature):
863
951
  return
864
952
  sys.stderr.write(
865
- '%s is passed but any of %s DAMON features is not available' %
953
+ '%s is passed but any of %s DAMON features is not available\n' %
866
954
  (arg, ', '.join(features)))
867
955
 
868
956
  def warn_unsupported_damon_features_for(args):
@@ -870,6 +958,19 @@ def warn_unsupported_damon_features_for(args):
870
958
  if args.sample_primitives is not None:
871
959
  warn_for('--sample_primitives', 'sysfs/damon_sample_control')
872
960
 
961
+ if args.probe_filter != []:
962
+ warn_for('--probe_filter', 'sysfs/attrs_monitoring')
963
+
964
+ # 7.2
965
+ if _damon.damos_action_collapse in args.damos_action:
966
+ warn_for('--damos_action collapse', 'sysfs/damos_action_collapse')
967
+ for quota_goal in args.damos_quota_goal:
968
+ metric = quota_goal[0]
969
+ if metric == _damon.qgoal_node_eligible_mem_bp:
970
+ warn_for('--damos_quota_goal %s' % metric,
971
+ 'sysfs/damos_quota_goal_node_eligible_mem_bp')
972
+
973
+ # 7.1
873
974
  if args.damos_quota_goal_tuner != []:
874
975
  warn_for('--damos_quota_goal_tuner', 'sysfs/damos_quota_goal_tuner')
875
976
  if args.pause_ctx is not None:
@@ -1273,6 +1374,21 @@ def set_monitoring_damos_common_args(parser, hide_help=False):
1273
1374
  action='append',
1274
1375
  help='monitoring operations set'
1275
1376
  if not hide_help else argparse.SUPPRESS)
1377
+
1378
+ parser.add_argument('--probe_filter', nargs='+', action='append',
1379
+ default=[],
1380
+ metavar='<<allow|reject> [non] <type> [option]...>',
1381
+ help='data attribute monitoring probe filter'
1382
+ if not hide_help else argparse.SUPPRESS)
1383
+ parser.add_argument(
1384
+ '--nr_probe_filters', type=int, nargs='+', metavar='<integer>',
1385
+ help='number of filters for each probe (in order)'
1386
+ if not hide_help else argparse.SUPPRESS)
1387
+ parser.add_argument(
1388
+ '--nr_probes', type=int, nargs='+', metavar='<integer>',
1389
+ help='number of probes for each context (in order)'
1390
+ if not hide_help else argparse.SUPPRESS)
1391
+
1276
1392
  parser.add_argument(
1277
1393
  '--pause_ctx', type=int, nargs='+', metavar='<context index>',
1278
1394
  help='contexts to pause')
@@ -327,15 +327,26 @@ features_list = [
327
327
  upstream_status='merged in v7.0-rc1 (4835e2871321f)',
328
328
  upstreamed_version='7.0'),
329
329
 
330
+ # v7.1-rc1 release: Sun Apr 26 14:19:00 2026 -0700
330
331
  DamonFeature(name='sysfs/damos_quota_goal_tuner',
332
+ upstream_status='merged in v7.1-rc1 (8719c59c4b928)',
333
+ upstreamed_version='7.1'),
334
+
335
+ DamonFeature(name='sysfs/damos_action_collapse',
336
+ upstream_status='merged in mm.git',
337
+ upstreamed_version='none'),
338
+ DamonFeature(name='sysfs/damos_quota_goal_node_eligible_mem_bp',
331
339
  upstream_status='merged in mm.git',
332
340
  upstreamed_version='none'),
333
-
334
341
  DamonFeature(name='sysfs/ctx_pause',
335
- upstream_status='wip on damon/next',
342
+ upstream_status='merged in mm.git',
336
343
  upstreamed_version='none'),
337
344
  DamonFeature(name='sysfs/damos_quota_fail_charge_ratio',
338
- upstream_status='wip on damon/next',
345
+ upstream_status='merged in mm.git',
346
+ upstreamed_version='none'),
347
+
348
+ DamonFeature(name='sysfs/attrs_monitoring',
349
+ upstream_status='hacking on damon/next',
339
350
  upstreamed_version='none'),
340
351
 
341
352
  DamonFeature(name='sysfs/damon_sample_control',
@@ -570,6 +570,52 @@ def write_ops_attrs_dir(dir_path, ops_attrs):
570
570
  if err is not None:
571
571
  return err
572
572
 
573
+ def write_probe_filter_dir(dir_path, filter):
574
+ err = _damo_fs.write_file(
575
+ os.path.join(dir_path, 'type'), filter.filter_type)
576
+ if err is not None:
577
+ return err
578
+ err = _damo_fs.write_file(os.path.join(dir_path, 'matching'),
579
+ 'Y' if filter.matching else 'N')
580
+ if err is not None:
581
+ return err
582
+ err = _damo_fs.write_file(os.path.join(dir_path, 'allow'),
583
+ 'Y' if filter.allow else 'N')
584
+ if err is not None:
585
+ return err
586
+ if filter.filter_type == 'memcg':
587
+ err = _damo_fs.write_file(os.path.join(dir_path, 'path'), filter.path)
588
+ if err is not None:
589
+ return err
590
+ return None
591
+
592
+ def write_probe_dir(dir_path, probe):
593
+ filters_dir = os.path.join(dir_path, 'filters')
594
+ err = ensure_nr_file_for(os.path.join(filters_dir, 'nr_filters'),
595
+ probe.filters)
596
+ if err is not None:
597
+ return err
598
+
599
+ for idx, filter in enumerate(probe.filters):
600
+ err = write_probe_filter_dir(
601
+ os.path.join(filters_dir, '%d' % idx), filter)
602
+ if err is not None:
603
+ return err
604
+ return None
605
+
606
+ def write_probes_dir(dir_path, probes):
607
+ if not os.path.isdir(dir_path):
608
+ return None
609
+ err = ensure_nr_file_for(os.path.join(dir_path, 'nr_probes'), probes)
610
+ if err is not None:
611
+ return err
612
+
613
+ for idx, probe in enumerate(probes):
614
+ err = write_probe_dir(os.path.join(dir_path, '%d' % idx), probe)
615
+ if err is not None:
616
+ return err
617
+ return None
618
+
573
619
  def write_sample_filter_dir(dir_path, sample_filter):
574
620
  err = _damo_fs.write_file(
575
621
  os.path.join(dir_path, 'type'), sample_filter.filter_type)
@@ -672,6 +718,11 @@ def write_monitoring_attrs_dir(dir_path, context):
672
718
  if err is not None:
673
719
  return err
674
720
 
721
+ err = write_probes_dir(
722
+ os.path.join(dir_path, 'probes'), context.probes)
723
+ if err is not None:
724
+ return err
725
+
675
726
  return write_sample_control_dir(
676
727
  os.path.join(dir_path, 'sample'), context.sample_control)
677
728
 
@@ -930,14 +981,28 @@ def files_content_to_damos_stats(files_content):
930
981
  int(files_content['qt_exceeds']),
931
982
  nr_snapshots=nr_snapshots, max_nr_snapshots=max_nr_snapshots)
932
983
 
984
+ def files_content_to_probe_hits(files_content):
985
+ hits = []
986
+ for kv in number_sorted_dirs(files_content):
987
+ hits.append(int(kv['hits']))
988
+ return hits
989
+
933
990
  def files_content_to_damos_tried_regions(files_content):
934
- return [_damon.DamonRegion(
935
- int(kv['start']), int(kv['end']),
936
- int(kv['nr_accesses']), _damon.unit_samples,
937
- int(kv['age']), _damon.unit_aggr_intervals,
938
- int(kv['sz_filter_passed'])
939
- if 'sz_filter_passed' in kv else None,
940
- ) for kv in number_sorted_dirs(files_content)]
991
+ regions = []
992
+ for kv in number_sorted_dirs(files_content):
993
+ if 'probes' in kv:
994
+ probe_hits = files_content_to_probe_hits(kv['probes'])
995
+ else:
996
+ probe_hits = []
997
+ regions.append(
998
+ _damon.DamonRegion(
999
+ int(kv['start']), int(kv['end']),
1000
+ int(kv['nr_accesses']), _damon.unit_samples,
1001
+ int(kv['age']), _damon.unit_aggr_intervals,
1002
+ int(kv['sz_filter_passed'])
1003
+ if 'sz_filter_passed' in kv else None,
1004
+ probe_hits=probe_hits))
1005
+ return regions
941
1006
 
942
1007
  def files_content_to_damos_dest(files_content):
943
1008
  return _damon.DamosDest(
@@ -985,6 +1050,29 @@ def files_content_to_target(files_content):
985
1050
  regions = files_content_to_regions(files_content['regions'])
986
1051
  return _damon.DamonTarget(pid, regions, obsolete=obsolete)
987
1052
 
1053
+ def files_content_to_damon_filter(files_content):
1054
+ path = None
1055
+ if 'path' in files_content:
1056
+ files_content.get('path').strip()
1057
+ return _damon.DamonFilter(
1058
+ filter_type=files_content['type'].strip(),
1059
+ matching=files_content['matching'].strip(),
1060
+ allow=files_content['allow'].strip(), path=path)
1061
+
1062
+ def files_content_to_probe(files_content):
1063
+ filters_content = files_content['filters']
1064
+ filters = []
1065
+ for kv in number_sorted_dirs(filters_content):
1066
+ filter = files_content_to_damon_filter(kv)
1067
+ filters.append(filter)
1068
+ return _damon.DamonProbe(filters=filters)
1069
+
1070
+ def files_content_to_probes(files_content):
1071
+ probes = []
1072
+ for kv in number_sorted_dirs(files_content):
1073
+ probes.append(files_content_to_probe(kv))
1074
+ return probes
1075
+
988
1076
  def files_content_to_sample_filter(files_content):
989
1077
  filter_type = files_content['type'].strip()
990
1078
  matching = files_content['matching'].strip()
@@ -1037,6 +1125,11 @@ def files_content_to_context(files_content):
1037
1125
  nr_regions = _damon.DamonNrRegionsRange(
1038
1126
  int(nr_regions_content['min']),
1039
1127
  int(nr_regions_content['max']))
1128
+ if 'probes' in files_content['monitoring_attrs']:
1129
+ probes = files_content_to_probes(
1130
+ files_content['monitoring_attrs']['probes'])
1131
+ else:
1132
+ probes = []
1040
1133
  if 'sample' in mon_attrs_content:
1041
1134
  sample_control = files_content_to_sample_control(
1042
1135
  mon_attrs_content['sample'])
@@ -1066,7 +1159,7 @@ def files_content_to_context(files_content):
1066
1159
 
1067
1160
  return _damon.DamonCtx(ops, targets, intervals, nr_regions, schemes,
1068
1161
  ops_attrs=ops_attrs, sample_control=sample_control,
1069
- pause=pause, addr_unit=addr_unit)
1162
+ pause=pause, addr_unit=addr_unit, probes=probes)
1070
1163
 
1071
1164
  def files_content_to_kdamond(files_content):
1072
1165
  contexts_content = files_content['contexts']
@@ -1339,6 +1432,14 @@ def mk_feature_supports_map():
1339
1432
  os.path.join(scheme_dir_of(0, 0, 0), 'quotas',
1340
1433
  'fail_charge_denom')):
1341
1434
  supports_map['sysfs/damos_quota_fail_charge_ratio'] = True
1435
+ # damos_collapse and damos_node_eligible_mem_bp will be upstreamed
1436
+ # together.
1437
+ supports_map['sysfs/damos_action_collapse'] = True
1438
+ supports_map['sysfs/damos_quota_goal_node_eligible_mem_bp'] = True
1439
+
1440
+ if os.path.isdir(
1441
+ os.path.join(ctx_dir_of(0, 0), 'monitoring_attrs', 'probes')):
1442
+ supports_map['sysfs/attrs_monitoring'] = True
1342
1443
 
1343
1444
  if os.path.isdir(os.path.join(ctx_dir_of(0, 0), 'operations_attrs')):
1344
1445
  supports_map['sysfs/ops_attrs'] = True
@@ -246,6 +246,11 @@ region_formatters = [
246
246
  lambda index, region, snapshot, record, fmt:
247
247
  temperature_str(region, fmt.raw_number, fmt),
248
248
  'access temperature of the region'),
249
+ Formatter(
250
+ '<probe hits>',
251
+ lambda index, region, snapshot, record, fmt:
252
+ ' '.join(['%d' % h for h in region.probe_hits]),
253
+ 'data attributese probe hit counts'),
249
254
  Formatter(
250
255
  '<filters passed bytes>',
251
256
  lambda index, region, snapshot, record, fmt:
@@ -3,7 +3,7 @@
3
3
  import os
4
4
  import subprocess
5
5
 
6
- __version__ = '3.2.2'
6
+ __version__ = '3.2.4'
7
7
 
8
8
  def get_release_version():
9
9
  return __version__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: damo
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: DAMON user-space tool
5
5
  Home-page: https://github.com/damonitor/damo
6
6
  Author: SeongJae Park
@@ -71,7 +71,7 @@ The second and last commands will show the access pattern of your workload,
71
71
  like below:
72
72
 
73
73
  ![masim_stairs_snapshot](images/masim_stairs_snapshot.png)
74
- ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.2/images/masim_stairs_heatmap_ascii.png)
74
+ ![masim stairs heatmap in ascii](https://raw.githubusercontent.com/damonitor/damo/v3.2.4/images/masim_stairs_heatmap_ascii.png)
75
75
 
76
76
 
77
77
  FAQs
@@ -90,7 +90,7 @@ Where can I get more detailed usage?
90
90
  ------------------------------------
91
91
 
92
92
  The below sections provide quick introductions for `damo`'s major features.
93
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
93
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
94
94
 
95
95
 
96
96
  What does the version numbers mean?
@@ -115,7 +115,7 @@ We try our best to make `damo` stable and doesn't introduce regressions to
115
115
  users. However, nothing goes forever. Sometimes, some features will be
116
116
  deprecated. Some features will have longer support more than others.
117
117
 
118
- In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) and not explicitly
118
+ In short, features that documented on [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) and not explicitly
119
119
  marked as experimental will be better supported, and provides at least three
120
120
  months of deprecation grace period. Within the grace period, users can ask
121
121
  extension of the support. Even after the grace period, please reach out to
@@ -138,7 +138,7 @@ Quick Intro for Major Features
138
138
  ==============================
139
139
 
140
140
  Below are quick introductions for `damo`'s major features.
141
- For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.2/USAGE.md) file.
141
+ For more detailed usage, please refer to [USAGE.md](https://github.com/damonitor/damo/blob/v3.2.4/USAGE.md) file.
142
142
 
143
143
 
144
144
  Snapshot Data Access Pattern
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes