alloc_tag: add ioctl filters to /proc/allocinfo

Extend the capability of the IOCTL mechanism to filter allocations based
on tag's module name, function name, file name and line number.

Link: https://lore.kernel.org/6a6100c0c58cb2911f39126b9fe177a8c17db16f.1783532853.git.abhishekbapat@google.com
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
Acked-by: Hao Ge <hao.ge@linux.dev>
Acked-by: Suren Baghdasaryan <surenb@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Kent Overstreet <kent.overstreet@linux.dev>
Cc: Sourav Panda <souravpanda@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Abhishek Bapat
2026-07-08 18:01:24 +00:00
committed by Andrew Morton
parent 1d581ab234
commit 5732a4e4c1
2 changed files with 89 additions and 5 deletions

View File

@@ -45,8 +45,32 @@ struct allocinfo_tag_data {
struct allocinfo_counter counter;
};
enum {
ALLOCINFO_FILTER_MODNAME,
ALLOCINFO_FILTER_FUNCTION,
ALLOCINFO_FILTER_FILENAME,
ALLOCINFO_FILTER_LINENO,
__ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
};
#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME)
#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION)
#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME)
#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO)
#define ALLOCINFO_FILTER_MASKS \
((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
struct allocinfo_filter {
__u64 mask; /* bitmask of the filter fields used */
struct allocinfo_tag fields;
};
struct allocinfo_get_at {
__u64 pos; /* input */
/* inputs */
__u64 pos;
struct allocinfo_filter filter;
/* output */
struct allocinfo_tag_data data;
};

View File

@@ -62,6 +62,7 @@ struct allocinfo_private {
struct codetag_iterator iter;
struct codetag_iterator reported_iter;
bool print_header;
struct allocinfo_filter filter;
/* ioctl uses a separate iterator not to interfere with reads */
struct codetag_iterator ioctl_iter;
bool positioned; /* seq_open_private() sets to 0 */
@@ -204,6 +205,12 @@ static void allocinfo_copy_str(char *dest, const char *src)
strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
}
/* Compare two strings and only consider the trimmed suffix if s1 is too long */
static int allocinfo_cmp_str(const char *str, const char *template)
{
return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
}
/*
* Populates the UAPI allocinfo_tag_data structure with active runtime
* profiling counters extracted from the given kernel codetag.
@@ -243,6 +250,40 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
return 0;
}
/*
* Verifies whether a given codetag satisfies the active filtering criteria by
* matching its characteristics against the specified filter.
*/
static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
{
if (!filter || !filter->mask)
return true;
if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
/* user wants to filter by modname but ct->modname is NULL */
if (!ct->modname) {
/* validate if user was attempting to filter for built-in allocations */
if (filter->fields.modname[0] != '\0')
return false;
} else if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
return false;
}
if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) &&
ct->function && allocinfo_cmp_str(ct->function, filter->fields.function))
return false;
if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) &&
ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename))
return false;
if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) &&
ct->lineno != filter->fields.lineno)
return false;
return true;
}
/*
* Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
* profiling data and returns it to userspace.
@@ -251,29 +292,46 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
{
struct allocinfo_private *priv;
struct codetag *ct;
__u64 pos;
struct allocinfo_get_at params = {0};
__u64 skip_count;
if (copy_from_user(&params, arg, sizeof(params)))
return -EFAULT;
if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
return -EINVAL;
priv = m->private;
pos = params.pos;
mutex_lock(&priv->ioctl_lock);
codetag_lock_module_list(alloc_tag_cttype);
if (pos >= codetag_get_count(alloc_tag_cttype)) {
if (params.pos >= codetag_get_count(alloc_tag_cttype)) {
codetag_unlock_module_list(alloc_tag_cttype);
mutex_unlock(&priv->ioctl_lock);
return -ENOENT;
}
skip_count = params.pos;
if (params.filter.mask)
priv->filter = params.filter;
else
priv->filter.mask = 0;
/* Find the codetag */
priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
ct = codetag_next_ct(&priv->ioctl_iter);
while (ct && pos--)
while (ct) {
if (matches_filter(ct, &priv->filter)) {
if (skip_count == 0)
break;
skip_count--;
}
ct = codetag_next_ct(&priv->ioctl_iter);
}
if (ct) {
allocinfo_to_params(ct, &params.data);
priv->positioned = true;
@@ -314,6 +372,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
}
ct = codetag_next_ct(&priv->ioctl_iter);
while (ct && !matches_filter(ct, &priv->filter))
ct = codetag_next_ct(&priv->ioctl_iter);
if (ct)
allocinfo_to_params(ct, &params);