alloc_tag: add size-based filtering to ioctl

Extend the allocinfo filtering mechanism to allow users to filter tags
based on the total number of bytes allocated [min_size, max_size].  The
size range is inclusive.

Filtering by size involves retrieving allocinfo per-CPU counters, which is
an expensive operation.  Hence, the performance of size-based filtering
will be worse than other filters.

Link: https://lore.kernel.org/0a7653b70ae0d64e967fbea0e933bc35f8ac656e.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:25 +00:00 committed by Andrew Morton
parent 5732a4e4c1
commit 6f6769ea88
2 changed files with 58 additions and 14 deletions

View File

@ -50,13 +50,17 @@ enum {
ALLOCINFO_FILTER_FUNCTION,
ALLOCINFO_FILTER_FILENAME,
ALLOCINFO_FILTER_LINENO,
__ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
ALLOCINFO_FILTER_MIN_SIZE,
ALLOCINFO_FILTER_MAX_SIZE,
__ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE
};
#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_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE)
#define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE)
#define ALLOCINFO_FILTER_MASKS \
((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
@ -64,6 +68,8 @@ enum {
struct allocinfo_filter {
__u64 mask; /* bitmask of the filter fields used */
struct allocinfo_tag fields;
__u64 min_size;
__u64 max_size;
};
struct allocinfo_get_at {

View File

@ -211,16 +211,20 @@ static int allocinfo_cmp_str(const char *str, const char *template)
return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
}
/* Fetch the per-CPU counters */
static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct)
{
return alloc_tag_read(ct_to_alloc_tag(ct));
}
/*
* Populates the UAPI allocinfo_tag_data structure with active runtime
* profiling counters extracted from the given kernel codetag.
*/
static void allocinfo_to_params(struct codetag *ct,
struct allocinfo_tag_data *data)
struct allocinfo_tag_data *data,
struct alloc_tag_counters *counters)
{
struct alloc_tag *tag = ct_to_alloc_tag(ct);
struct alloc_tag_counters counter = alloc_tag_read(tag);
if (ct->modname)
allocinfo_copy_str(data->tag.modname, ct->modname);
else
@ -228,9 +232,9 @@ static void allocinfo_to_params(struct codetag *ct,
allocinfo_copy_str(data->tag.function, ct->function);
allocinfo_copy_str(data->tag.filename, ct->filename);
data->tag.lineno = ct->lineno;
data->counter.bytes = counter.bytes;
data->counter.calls = counter.calls;
data->counter.accurate = !alloc_tag_is_inaccurate(tag);
data->counter.bytes = counters->bytes;
data->counter.calls = counters->calls;
data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct));
}
/*
@ -254,7 +258,9 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
* 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)
static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter,
struct alloc_tag_counters *counters,
bool *fetched_counters)
{
if (!filter || !filter->mask)
return true;
@ -281,6 +287,19 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
ct->lineno != filter->fields.lineno)
return false;
if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) {
if (!*fetched_counters) {
*counters = allocinfo_prefetch_counters(ct);
*fetched_counters = true;
}
if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&
counters->bytes < filter->min_size)
return false;
if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&
counters->bytes > filter->max_size)
return false;
}
return true;
}
@ -294,6 +313,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
struct codetag *ct;
struct allocinfo_get_at params = {0};
__u64 skip_count;
struct alloc_tag_counters counters;
bool fetched_counters;
if (copy_from_user(&params, arg, sizeof(params)))
return -EFAULT;
@ -301,6 +322,11 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
return -EINVAL;
if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&
(params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&
params.filter.min_size > params.filter.max_size)
return -EINVAL;
priv = m->private;
mutex_lock(&priv->ioctl_lock);
@ -324,7 +350,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
ct = codetag_next_ct(&priv->ioctl_iter);
while (ct) {
if (matches_filter(ct, &priv->filter)) {
fetched_counters = false;
if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) {
if (skip_count == 0)
break;
skip_count--;
@ -333,7 +360,9 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
}
if (ct) {
allocinfo_to_params(ct, &params.data);
if (!fetched_counters)
counters = allocinfo_prefetch_counters(ct);
allocinfo_to_params(ct, &params.data, &counters);
priv->positioned = true;
}
@ -359,6 +388,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
struct codetag *ct;
struct allocinfo_tag_data params;
int ret = 0;
struct alloc_tag_counters counters;
bool fetched_counters;
memset(&params, 0, sizeof(params));
priv = m->private;
@ -372,11 +403,18 @@ 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))
while (ct) {
fetched_counters = false;
if (matches_filter(ct, &priv->filter, &counters, &fetched_counters))
break;
ct = codetag_next_ct(&priv->ioctl_iter);
if (ct)
allocinfo_to_params(ct, &params);
}
if (ct) {
if (!fetched_counters)
counters = allocinfo_prefetch_counters(ct);
allocinfo_to_params(ct, &params, &counters);
}
if (!ct) {
priv->positioned = false;
ret = -ENOENT;