All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.