From: Akinobu Mita <akinobu.mita@gmail.com>
To: damon@lists.linux.dev
Cc: linux-perf-users@vger.kernel.org, sj@kernel.org,
ravis.opensrc@gmail.com, akinobu.mita@gmail.com
Subject: [RFC PATCH 2/2] damo: add --perf_event option
Date: Mon, 22 Jun 2026 22:49:33 +0900 [thread overview]
Message-ID: <20260622134933.35773-3-akinobu.mita@gmail.com> (raw)
In-Reply-To: <20260622134933.35773-1-akinobu.mita@gmail.com>
The argument for the newly added --perf_event can be the same PMU event as
the 'perf record' -e option.
The appropriate PMU event can be found in the following way:
```
$ sudo perf mem record sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.030 MB perf.data (12 samples) ]
$ sudo perf evlist
cpu/mem-loads,ldlat=30/P
cpu/mem-stores/P
```
In this case, you can set up perf events by starting damo as follows:
```
$ sudo damo start \
--perf_event "cpu/mem-loads,ldlat=30,freq=5000/P" \
--perf_event "cpu/mem-stores,freq=5000/P" \
...
```
In this example, the sampling frequency is also specified by the common
parameter 'freq'.
Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
---
src/_damo_fmt_str.py | 5 ++
src/_damon.py | 106 +++++++++++++++++++++++++++++++++-
src/_damon_args.py | 79 +++++++++++++++++++++++--
src/_damon_sysfs.py | 133 ++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 314 insertions(+), 9 deletions(-)
diff --git a/src/_damo_fmt_str.py b/src/_damo_fmt_str.py
index 6c51e24a..d93d5ba8 100644
--- a/src/_damo_fmt_str.py
+++ b/src/_damo_fmt_str.py
@@ -257,6 +257,11 @@ def text_to_nr(txt):
pass
return float(new_txt)
+def text_to_hex(txt):
+ if type(txt) == int:
+ return txt
+ return int(txt, 16)
+
def try_common_input(txt, min_val=0, max_val=ulong_max):
'return success and number'
if txt == 'min':
diff --git a/src/_damon.py b/src/_damon.py
index 8cfc61e3..52653fc5 100644
--- a/src/_damon.py
+++ b/src/_damon.py
@@ -261,6 +261,93 @@ class DamonNrRegionsRange:
('max', _damo_fmt_str.format_nr(self.maximum, raw)),
])
+class DamonPerfEvent:
+ attr_type = None
+ config = None
+ config1 = None
+ config2 = None
+ freq = None
+ sample_period = None
+ sample_freq = None
+ sample_phys_addr = None
+ sample_weight_struct = None
+ exclude_kernel = None
+ exclude_hv = None
+ precise_ip = None
+ wakeup_events = None
+
+ def __init__(self, attr_type='0x0', config='0x0', config1='0x0',
+ config2='0x0', freq=1, sample_period=0, sample_freq=0,
+ sample_phys_addr=0, sample_weight_struct=0, exclude_kernel=0,
+ exclude_hv=0, precise_ip=2, wakeup_events=0):
+ self.attr_type = _damo_fmt_str.text_to_hex(attr_type)
+ self.config = _damo_fmt_str.text_to_hex(config)
+ self.config1 = _damo_fmt_str.text_to_hex(config1)
+ self.config2 = _damo_fmt_str.text_to_hex(config2)
+ self.freq = _damo_fmt_str.text_to_nr(freq)
+ self.sample_period = _damo_fmt_str.text_to_nr(sample_period)
+ self.sample_freq = _damo_fmt_str.text_to_nr(sample_freq)
+ self.sample_phys_addr = _damo_fmt_str.text_to_nr(sample_phys_addr)
+ self.sample_weight_struct = _damo_fmt_str.text_to_nr(sample_weight_struct)
+ self.exclude_kernel = _damo_fmt_str.text_to_nr(exclude_kernel)
+ self.exclude_hv = _damo_fmt_str.text_to_nr(exclude_hv)
+ self.precise_ip = _damo_fmt_str.text_to_nr(precise_ip)
+ self.wakeup_events = _damo_fmt_str.text_to_nr(wakeup_events)
+
+ def to_str(self, raw):
+ lines = []
+ lines.append('type: %#x' % self.attr_type)
+ lines.append('config: [%#x, %#x, %#x]' % self.config, self.config1, self.config2)
+ lines.append('freq: %s' % _damo_fmt_str.format_nr(self.freq, raw))
+ lines.append('sample_period: %s' % _damo_fmt_str.format_nr(self.sample_period, raw))
+ lines.append('sample_freq: %s' % _damo_fmt_str.format_nr(self.sample_freq, raw))
+ lines.append('sample_phys_addr: %s' % _damo_fmt_str.format_nr(self.sample_phys_addr, raw))
+ lines.append('sample_weight_struct: %s' % _damo_fmt_str.format_nr(self.sample_weight_struct, raw))
+ lines.append('exclude_kernel: %s' % _damo_fmt_str.format_nr(self.exclude_kernel, raw))
+ lines.append('exclude_hv: %s' % _damo_fmt_str.format_nr(self.exclude_hv, raw))
+ lines.append('precise_ip: %s' % _damo_fmt_str.format_nr(self.precise_ip, raw))
+ lines.append('wakeup_events: %s' % _damo_fmt_str.format_nr(self.wakeup_events, raw))
+ return '\n'.join(lines)
+
+ def __str__(self):
+ return self.to_str(False)
+
+ def __eq__(self, other):
+ return type(self) == type(other) and '%s' % self == '%s' % other
+
+ @classmethod
+ def from_kvpairs(cls, kvpairs):
+ return DamonPerfEvent(attr_type=kvpairs['type'],
+ config=kvpairs['config'],
+ config1=kvpairs['config1'],
+ config2=kvpairs['config2'],
+ freq=kvpairs['freq'],
+ sample_period=kvpairs['sample_period'],
+ sample_freq=kvpairs['sample_freq'],
+ sample_phys_addr=kvpairs['sample_phys_addr'],
+ sample_weight_struct=kvpairs['sample_weight_struct'],
+ exclude_kernel=kvpairs['exclude_kernel'],
+ exclude_hv=kvpairs['exclude_hv'],
+ precise_ip=kvpairs['precise_ip'],
+ wakeup_events=kvpairs['wakeup_events'])
+
+ def to_kvpairs(self, raw=False):
+ return collections.OrderedDict([
+ ('type', '%#x' % self.attr_type),
+ ('config', '%#x' % self.config),
+ ('config1', '%#x' % self.config1),
+ ('config2', '%#x' % self.config2),
+ ('freq', _damo_fmt_str.format_nr(self.freq, raw)),
+ ('sample_period', _damo_fmt_str.format_nr(self.sample_period, raw)),
+ ('sample_freq', _damo_fmt_str.format_nr(self.sample_freq, raw)),
+ ('sample_phys_addr', _damo_fmt_str.format_nr(self.sample_phys_addr, raw)),
+ ('sample_weight_struct', _damo_fmt_str.format_nr(self.sample_weight_struct, raw)),
+ ('exclude_kernel', _damo_fmt_str.format_nr(self.exclude_kernel, raw)),
+ ('exclude_hv', _damo_fmt_str.format_nr(self.exclude_hv, raw)),
+ ('precise_ip', _damo_fmt_str.format_nr(self.precise_ip, raw)),
+ ('wakeup_events', _damo_fmt_str.format_nr(self.wakeup_events, raw)),
+ ])
+
damon_filter_type_cpumask = 'cpumask'
damon_filter_type_threads = 'threads'
damon_filter_type_write = 'write'
@@ -359,14 +446,18 @@ class DamonPrimitivesEnabled:
class DamonSampleControl:
primitives_enabled = None
sample_filters = None
+ perf_events = None
- def __init__(self, primitives_enabled=None, sample_filters=None):
+ def __init__(self, primitives_enabled=None, sample_filters=None, perf_events=None):
if primitives_enabled is None:
primitives_enabled = DamonPrimitivesEnabled()
if sample_filters is None:
sample_filters = []
+ if perf_events is None:
+ perf_events = []
self.primitives_enabled = primitives_enabled
self.sample_filters = sample_filters
+ self.perf_events = perf_events
def to_str(self, raw):
lines = [
@@ -375,6 +466,10 @@ class DamonSampleControl:
lines.append('Filters')
for filter in self.sample_filters:
lines.append('- %s' % filter.to_str(raw))
+ if len(self.perf_events) > 0:
+ lines.append('PerfEvents')
+ for perf_event in self.perf_events:
+ lines.append('- %s' % perf_event.to_str(raw))
return '\n'.join(lines)
def __str__(self):
@@ -383,7 +478,8 @@ class DamonSampleControl:
def __eq__(self, other):
return type(self) == type(other) and \
self.primitives_enabled == other.primitives_enabled and \
- self.sample_filters == other.sample_filters
+ self.sample_filters == other.sample_filters and \
+ self.perf_events == other.perf_events
@classmethod
def from_kvpairs(cls, kv):
@@ -391,13 +487,17 @@ class DamonSampleControl:
primitives_enabled=DamonPrimitivesEnabled.from_kvpairs(
kv['primitives_enabled']),
sample_filters=[DamonSampleFilter.from_kvpairs(kvpairs)
- for kvpairs in kv['sample_filters']])
+ for kvpairs in kv['sample_filters']],
+ perf_events=[DamonPerfEvent.from_kvpairs(p)
+ for p in kv['perf_events']])
def to_kvpairs(self, raw=False):
return collections.OrderedDict([
('primitives_enabled', self.primitives_enabled.to_kvpairs(raw)),
('sample_filters', [
f.to_kvpairs(raw) for f in self.sample_filters]),
+ ('perf_events', [
+ p.to_kvpairs(raw) for p in self.perf_events])
])
unit_percent = 'percent'
diff --git a/src/_damon_args.py b/src/_damon_args.py
index c85e99be..8a652058 100644
--- a/src/_damon_args.py
+++ b/src/_damon_args.py
@@ -18,6 +18,12 @@ except ModuleNotFoundError:
# properly.
pass
+try:
+ import perf
+except:
+ print('Please install perf.cpython-*-linux-gnu.so', file=sys.stderr)
+ raise
+
import _damo_subproc
import _damo_sysinfo
import _damon
@@ -110,6 +116,33 @@ def damon_nr_regions_range_for(args_range, args_minr, args_maxr):
override_vals(nr_range, [args_minr, args_maxr])
return _damon.DamonNrRegionsRange(*nr_range)
+def damon_perf_events_for(args_perf_event, args_ops):
+ events = []
+ if args_perf_event:
+ for event in args_perf_event:
+ sample_phys_addr = 1 if args_ops == 'paddr' else 0
+ evlist = perf.parse_events(event[0])
+ evlist.config()
+ if len(evlist) != 1:
+ raise Exception('%s event is ambiguous' % event[0])
+ evsel = evlist[0]
+ sample_period = evsel.sample_period if evsel.freq == 0 else 0
+ sample_freq = evsel.sample_period if evsel.freq == 1 else 0
+ events.append(_damon.DamonPerfEvent(
+ attr_type=evsel.type,
+ config=evsel.config,
+ config1=evsel.config1,
+ config2=evsel.config2,
+ freq=evsel.freq,
+ sample_period=sample_period,
+ sample_freq=sample_freq,
+ sample_phys_addr=sample_phys_addr,
+ exclude_kernel=evsel.exclude_kernel,
+ exclude_hv=evsel.exclude_hv,
+ precise_ip=evsel.precise_ip,
+ ))
+ return events
+
def schemes_option_to_damos(schemes):
if os.path.isfile(schemes):
with open(schemes, 'r') as f:
@@ -540,10 +573,14 @@ def sample_control_to_ops_attrs_args(args, idx):
return '--sample_primitives of %s is not supported for now' % \
sample_primitives
-def build_sample_control_ops_attrs(args, idx):
+def build_sample_control_ops_attrs(args, idx, args_perf_event):
'''
Returns DamonSampleControl, OpsAttrs, and an error
'''
+ try:
+ perf_events = damon_perf_events_for(args_perf_event, args.ops[idx])
+ except Exception as e:
+ return None, None, 'invalid perf_event arguments (%s)' % e
err = sample_control_to_ops_attrs_args(args, idx)
if err is not None:
return None, None, err
@@ -595,10 +632,11 @@ def build_sample_control_ops_attrs(args, idx):
allow=True, tid_arr=tids))
sample_control = _damon.DamonSampleControl(
primitives_enabled=primitives_enabled,
- sample_filters=sample_filters)
+ sample_filters=sample_filters,
+ perf_events=perf_events)
return sample_control, None, None
-def damon_ctx_for(args, idx):
+def damon_ctx_for(args, idx, args_perf_event):
if args.ops[idx] is None:
if args.target_pid[idx] is None:
args.ops[idx] = 'paddr'
@@ -620,7 +658,7 @@ def damon_ctx_for(args, idx):
except Exception as e:
return None, 'invalid nr_regions arguments (%s)' % e
ops = args.ops[idx]
- sample_control, ops_attrs, err = build_sample_control_ops_attrs(args, idx)
+ sample_control, ops_attrs, err = build_sample_control_ops_attrs(args, idx, args_perf_event)
if err is not None:
return None, 'exp_ops_* handling fail (%s)' % err
pause = False
@@ -836,12 +874,35 @@ def gen_assign_probes(ctxs, args):
probe_idx += nr
return None
+def gen_assign_perf_events(args_perf_events, args):
+ nr_ctxs = get_nr_ctxs(args)
+ if args.nr_perf_events is None:
+ if nr_ctxs != 1 and len(args.perf_event) > 0:
+ return '--nr_perf_events is required'
+ args.nr_perf_events = [len(args.perf_event)]
+ args.nr_perf_events += [0] * (nr_ctxs - 1)
+ if sum(args.nr_perf_events) != len(args.perf_event):
+ return '--nr_perf_events mismatches number of perf_events (%d != %d)' % (
+ sum(args.nr_perf_events), len(args.perf_event))
+ if len(args.nr_perf_events) != nr_ctxs:
+ return '--nr_perf_events mismatches number of contexts (%d != %d)' % (
+ len(args.nr_perf_events), nr_ctxs)
+ perf_event_idx = 0
+ for nr in args.nr_perf_events:
+ args_perf_events.append(args.perf_event[perf_event_idx:perf_event_idx + nr])
+ perf_event_idx += nr
+ return None
+
def damon_ctxs_for(args):
fillup_none_ctx_args(args)
fillup_none_target_args(args)
+ args_perf_events = []
+ err = gen_assign_perf_events(args_perf_events, args)
+ if err is not None:
+ return None, err
ctxs = []
for idx in range(get_nr_ctxs(args)):
- ctx, err = damon_ctx_for(args, idx)
+ ctx, err = damon_ctx_for(args, idx, args_perf_events[idx])
if err is not None:
return None, err
ctxs.append(ctx)
@@ -1368,6 +1429,14 @@ def set_monitoring_attrs_argparser(parser, hide_help=False):
choices=['page_table', 'page_fault'], nargs='+',
help='access sampling primitives to use'
if not hide_help else argparse.SUPPRESS)
+ parser.add_argument('--perf_event', nargs='+',
+ metavar=('<event>'),
+ default=[], action='append',
+ help='monitoring perf_event'
+ if not hide_help else argparse.SUPPRESS)
+ parser.add_argument('--nr_perf_events', type=int, metavar='<number>', nargs='+',
+ help='number of perf events for each context (in order)'
+ if not hide_help else argparse.SUPPRESS)
def set_monitoring_damos_common_args(parser, hide_help=False):
parser.add_argument('--ops', choices=['vaddr', 'paddr', 'fvaddr'],
diff --git a/src/_damon_sysfs.py b/src/_damon_sysfs.py
index 387e6f96..ccff3e30 100644
--- a/src/_damon_sysfs.py
+++ b/src/_damon_sysfs.py
@@ -616,6 +616,102 @@ def write_probes_dir(dir_path, probes):
return err
return None
+def write_perf_event_dir(dir_path, perf_event):
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'type'),
+ '%#x' % perf_event.attr_type)
+ if err is not None:
+ return err
+
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'config'),
+ '%#x' % perf_event.config)
+ if err is not None:
+ return err
+
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'config1'),
+ '%#x' % perf_event.config1)
+ if err is not None:
+ return err
+
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'config2'),
+ '%#x' % perf_event.config2)
+ if err is not None:
+ return err
+
+ freq_path = os.path.join(dir_path, 'freq')
+ if os.path.isfile(freq_path):
+ err = _damo_fs.write_file(freq_path, '%d' % perf_event.freq)
+ if err is not None:
+ return err
+
+ sample_period_path = os.path.join(dir_path, 'sample_period')
+ if os.path.isfile(sample_period_path):
+ err = _damo_fs.write_file(sample_period_path,
+ '%d' % perf_event.sample_period)
+ if err is not None:
+ return err
+
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'sample_freq'),
+ '%d' % perf_event.sample_freq)
+ if err is not None:
+ return err
+
+ err = _damo_fs.write_file(
+ os.path.join(dir_path, 'sample_phys_addr'),
+ '%d' % perf_event.sample_phys_addr)
+ if err is not None:
+ return err
+
+ sample_weight_struct_path = os.path.join(dir_path, 'sample_weight_struct')
+ if os.path.isfile(sample_weight_struct_path):
+ err = _damo_fs.write_file(sample_weight_struct_path,
+ '%d' % perf_event.sample_weight_struct)
+ if err is not None:
+ return err
+
+ exclude_kernel_path = os.path.join(dir_path, 'exclude_kernel')
+ if os.path.isfile(exclude_kernel_path):
+ err = _damo_fs.write_file(exclude_kernel_path,
+ '%d' % perf_event.exclude_kernel)
+ if err is not None:
+ return err
+
+ exclude_hv_path = os.path.join(dir_path, 'exclude_hv')
+ if os.path.isfile(exclude_hv_path):
+ err = _damo_fs.write_file(exclude_hv_path,
+ '%d' % perf_event.exclude_hv)
+ if err is not None:
+ return err
+
+ precise_ip_path = os.path.join(dir_path, 'precise_ip')
+ if os.path.isfile(precise_ip_path):
+ err = _damo_fs.write_file(precise_ip_path,
+ '%d' % perf_event.precise_ip)
+ if err is not None:
+ return err
+
+ wakeup_events_path = os.path.join(dir_path, 'wakeup_events')
+ if os.path.isfile(wakeup_events_path):
+ err = _damo_fs.write_file(wakeup_events_path,
+ '%d' % perf_event.wakeup_events)
+ if err is not None:
+ return err
+
+def write_perf_events_dir(dir_path, perf_events):
+ err = ensure_nr_file_for(os.path.join(dir_path, 'nr_perf_events'), perf_events)
+ if err is not None:
+ return err
+
+ for idx, perf_event in enumerate(perf_events):
+ err = write_perf_event_dir(os.path.join(dir_path, '%d' % idx), perf_event)
+ if err is not None:
+ return err
+ return None
+
def write_sample_filter_dir(dir_path, sample_filter):
err = _damo_fs.write_file(
os.path.join(dir_path, 'type'), sample_filter.filter_type)
@@ -677,6 +773,12 @@ def write_sample_control_dir(dir_path, sample_control):
'Y' if sample_control.primitives_enabled.page_fault else 'N')
if err is not None:
return err
+
+ err = write_perf_events_dir(
+ os.path.join(dir_path, 'perf_events'), sample_control.perf_events)
+ if err is not None:
+ return err
+
return write_sample_filters_dir(
os.path.join(dir_path, 'filters'), sample_control.sample_filters)
@@ -1094,9 +1196,15 @@ def files_content_to_sample_control(files_content):
primitives_enabled = _damon.DamonPrimitivesEnabled(
page_table=page_table, page_fault=page_fault)
sample_filters = files_content_to_sample_filters(files_content['filters'])
+
+ perf_events_content = files_content['perf_events']
+ perf_events = [files_content_to_perf_event(content)
+ for content in numbered_dirs_content(
+ perf_events_content, 'nr_perf_events')]
return _damon.DamonSampleControl(
primitives_enabled=primitives_enabled,
- sample_filters=sample_filters)
+ sample_filters=sample_filters,
+ perf_events=perf_events)
def files_content_to_ops_attrs(files_content):
use_reports = files_content['use_reports'].strip()
@@ -1106,6 +1214,29 @@ def files_content_to_ops_attrs(files_content):
return _damon.OpsAttrs(use_reports=use_reports, write_only=write_only,
cpus=cpus, tids=tids)
+def files_content_to_perf_event(files_content):
+ freq = int(files_content['freq']) if 'freq' in files_content else 1
+ sample_period = int(files_content['sample_period']) if 'sample_period' in files_content else 0
+ sample_weight_struct = int(files_content['sample_weight_struct']) if 'sample_weight_struct' in files_content else 0
+ exclude_kernel = int(files_content['exclude_kernel']) if 'exclude_kernel' in files_content else 0
+ exclude_hv = int(files_content['exclude_hv']) if 'exclude_hv' in files_content else 0
+ precise_ip = int(files_content['precise_ip']) if 'precise_ip' in files_content else 2
+ wakeup_events = int(files_content['wakeup_events']) if 'wakeup_events' in files_content else 0
+ return _damon.DamonPerfEvent(
+ attr_type=int(files_content['type'], 16),
+ config=int(files_content['config'], 16),
+ config1=int(files_content['config1'], 16),
+ config2=int(files_content['config2'], 16),
+ freq=freq,
+ sample_period=sample_period,
+ sample_freq=int(files_content['sample_freq']),
+ sample_phys_addr=int(files_content['sample_phys_addr']),
+ sample_weight_struct=sample_weight_struct,
+ exclude_kernel=exclude_kernel,
+ exclude_hv=exclude_hv,
+ precise_ip=precise_ip,
+ wakeup_events=wakeup_events)
+
def files_content_to_context(files_content):
mon_attrs_content = files_content['monitoring_attrs']
intervals_content = mon_attrs_content['intervals']
--
2.43.0
next prev parent reply other threads:[~2026-06-22 13:50 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-22 13:49 [RFC PATCH 0/2] damo: support perf event configuration Akinobu Mita
2026-06-22 13:49 ` [RFC PATCH 1/2] perf python: Add access to various members of evsel Akinobu Mita
2026-06-22 13:49 ` Akinobu Mita [this message]
2026-06-22 15:24 ` [RFC PATCH 0/2] damo: support perf event configuration SeongJae Park
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260622134933.35773-3-akinobu.mita@gmail.com \
--to=akinobu.mita@gmail.com \
--cc=damon@lists.linux.dev \
--cc=linux-perf-users@vger.kernel.org \
--cc=ravis.opensrc@gmail.com \
--cc=sj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox