Linux Trace Kernel
 help / color / mirror / Atom feed
* [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing
@ 2026-08-30 11:07 hu.shengming
  2026-08-30 11:09 ` [RFC PATCH 1/4] ftrace: Generalize function task filter names hu.shengming
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: hu.shengming @ 2026-08-30 11:07 UTC (permalink / raw)
  To: rostedt, mhiramat
  Cc: mathieu.desnoyers, mark.rutland, corbet, skhan, rdunlap,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

Hi Steven and Masami,

This series adds two comm-based task filters for the function and
function_graph tracers:

  set_ftrace_comm
  set_ftrace_notrace_comm

Function tracing currently supports selecting tasks by PID. This makes
it difficult to configure tracing before a service starts, because it
does not have a PID yet. It is also inconvenient to keep tracing the
same service across restarts, as its PID may change. This use case was
suggested by Xuxin, a KSM reviewer.

The new filters match task comm names exactly. When both PID and comm 
include filters are active, a task must match both to be traced. A
match in either the PID or comm notrace filter excludes the task.

To avoid string matching in the function tracing fast path, the filters
are evaluated when a task is scheduled in. The result is stored in the
existing per-CPU cache used by PID filtering. Changing a filter refreshes
the cached result for currently running tasks. If a task's comm changes,
the new name is used the next time the task is scheduled in.

The first two patches prepare the existing PID-filtering infrastructure
by using general task-filter names and centralizing sched_switch probe
registration and per-CPU cache updates. The third patch adds the comm
filters, and the final patch documents their interface and interaction
with PID filters.

This is an RFC intended to discuss whether comm-based task filtering is
a useful direction for function tracing and whether this interface would
be suitable for eventual upstream inclusion. Additional selftests are
planned for the next revision. Feedback on the overall approach and
interface would be greatly appreciated.

Thanks,
Shengming

Shengming Hu (4):
  ftrace: Generalize function task filter names
  ftrace: Centralize task filter state updates
  ftrace: Add exact task comm filtering
  Documentation/ftrace: Document function comm filters

 Documentation/trace/ftrace.rst |  31 +++
 include/linux/ftrace.h         |   4 +-
 kernel/trace/Makefile          |   1 +
 kernel/trace/comm_list.c       | 314 +++++++++++++++++++++++++
 kernel/trace/comm_list.h       |  17 ++
 kernel/trace/fgraph.c          |   4 +-
 kernel/trace/ftrace.c          | 402 ++++++++++++++++++++++++++++++---
 kernel/trace/trace.c           |   5 +
 kernel/trace/trace.h           |  34 ++-
 kernel/trace/trace_events.c    |  16 +-
 kernel/trace/trace_functions.c |   2 +-
 kernel/trace/trace_pid.c       |   8 +-
 12 files changed, 791 insertions(+), 47 deletions(-)
 create mode 100644 kernel/trace/comm_list.c
 create mode 100644 kernel/trace/comm_list.h

-- 
2.25.1

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 1/4] ftrace: Generalize function task filter names
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
@ 2026-08-30 11:09 ` hu.shengming
  2026-08-30 11:10 ` ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates hu.shengming
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: hu.shengming @ 2026-08-30 11:09 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	rdunlap, linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

From: Shengming Hu <hu.shengming@zte.com.cn>

Function tracing decides whether to trace a task when it is scheduled
in and caches the result per CPU. This mechanism currently handles PID
filters, but it will also be used by other task filters.

Rename the shared enable check and sched_switch callback to use more
general task-filter names. Rename trace_ignore_this_task() to
trace_ignore_pid_task(), since that helper specifically checks PID
filters.

Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
---
 include/linux/ftrace.h         |  4 ++--
 kernel/trace/fgraph.c          |  4 ++--
 kernel/trace/ftrace.c          | 16 ++++++++--------
 kernel/trace/trace.h           |  2 +-
 kernel/trace/trace_events.c    | 16 ++++++++--------
 kernel/trace/trace_functions.c |  2 +-
 kernel/trace/trace_pid.c       |  8 ++++----
 7 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h
index 02bc5027523a..60e1dede1e68 100644
--- a/include/linux/ftrace.h
+++ b/include/linux/ftrace.h
@@ -331,7 +331,7 @@ ftrace_func_t ftrace_ops_get_func(struct ftrace_ops *ops);
  *            SAVE_REGS. If another ops with this flag set is already registered
  *            for any of the functions that this ops will be registered for, then
  *            this ops will fail to register or set_filter_ip.
- * PID     - Is affected by set_ftrace_pid (allows filtering on those pids)
+ * PID     - Is affected by function task filters
  * RCU     - Set when the ops can only be called when RCU is watching.
  * TRACE_ARRAY - The ops->private points to a trace_array descriptor.
  * PERMANENT - Set when the ops is permanent and should not be affected by
@@ -1251,7 +1251,7 @@ typedef int (*trace_func_graph_ent_t)(struct ftrace_graph_ent *,
 extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace,
 				   struct fgraph_ops *gops,
 				   struct ftrace_regs *fregs);
-bool ftrace_pids_enabled(struct ftrace_ops *ops);
+bool ftrace_task_filters_enabled(struct ftrace_ops *ops);

 #ifdef CONFIG_FUNCTION_GRAPH_TRACER

diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c
index 40d373d65f9b..c8bbba4eecf0 100644
--- a/kernel/trace/fgraph.c
+++ b/kernel/trace/fgraph.c
@@ -1223,7 +1223,7 @@ void fgraph_update_pid_func(void)
 	list_for_each_entry(op, &graph_ops.subop_list, list) {
 		if (op->flags & FTRACE_OPS_FL_PID) {
 			gops = container_of(op, struct fgraph_ops, ops);
-			gops->entryfunc = ftrace_pids_enabled(op) ?
+			gops->entryfunc = ftrace_task_filters_enabled(op) ?
 				fgraph_pid_func : gops->saved_func;
 			if (ftrace_graph_active == 1)
 				static_call_update(fgraph_func, gops->entryfunc);
@@ -1382,7 +1382,7 @@ int register_ftrace_graph(struct fgraph_ops *gops)
 	/* Always save the function, and reset at unregistering */
 	gops->saved_func = gops->entryfunc;
 #ifdef CONFIG_DYNAMIC_FTRACE
-	if (ftrace_pids_enabled(&gops->ops))
+	if (ftrace_task_filters_enabled(&gops->ops))
 		gops->entryfunc = fgraph_pid_func;
 #endif

diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index f9d80c7bd9f1..479ea004adc3 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -99,7 +99,7 @@ struct ftrace_ops *function_trace_op __read_mostly = &ftrace_list_end;
 /* What to set function_trace_op to */
 static struct ftrace_ops *set_function_trace_op;

-bool ftrace_pids_enabled(struct ftrace_ops *ops)
+bool ftrace_task_filters_enabled(struct ftrace_ops *ops)
 {
 	struct trace_array *tr;

@@ -359,7 +359,7 @@ int __register_ftrace_function(struct ftrace_ops *ops)
 	/* Always save the function, and reset at unregistering */
 	ops->saved_func = ops->func;

-	if (ftrace_pids_enabled(ops))
+	if (ftrace_task_filters_enabled(ops))
 		ops->func = ftrace_pid_func;

 	ftrace_update_trampoline(ops);
@@ -400,7 +400,7 @@ static void ftrace_update_pid_func(void)

 	do_for_each_ftrace_op(op, ftrace_ops_list) {
 		if (op->flags & FTRACE_OPS_FL_PID) {
-			op->func = ftrace_pids_enabled(op) ?
+			op->func = ftrace_task_filters_enabled(op) ?
 				ftrace_pid_func : op->saved_func;
 			ftrace_update_trampoline(op);
 		}
@@ -8638,7 +8638,7 @@ ftrace_func_t ftrace_ops_get_func(struct ftrace_ops *ops)
 }

 static void
-ftrace_filter_pid_sched_switch_probe(void *data, bool preempt,
+ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
 				     struct task_struct *prev,
 				     struct task_struct *next,
 				     unsigned int prev_state)
@@ -8650,7 +8650,7 @@ ftrace_filter_pid_sched_switch_probe(void *data, bool preempt,
 	pid_list = rcu_dereference_sched(tr->function_pids);
 	no_pid_list = rcu_dereference_sched(tr->function_no_pids);

-	if (trace_ignore_this_task(pid_list, no_pid_list, next))
+	if (trace_ignore_pid_task(pid_list, no_pid_list, next))
 		this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
 			       FTRACE_PID_IGNORE);
 	else
@@ -8720,7 +8720,7 @@ static void clear_ftrace_pids(struct trace_array *tr, int type)

 	/* See if the pids still need to be checked after this */
 	if (!still_need_pid_events(type, pid_list, no_pid_list)) {
-		unregister_trace_sched_switch(ftrace_filter_pid_sched_switch_probe, tr);
+		unregister_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
 		for_each_possible_cpu(cpu)
 			per_cpu_ptr(tr->array_buffer.data, cpu)->ftrace_ignore_pid = FTRACE_PID_TRACE;
 	}
@@ -8920,7 +8920,7 @@ static void ignore_task_cpu(void *data)
 	no_pid_list = rcu_dereference_protected(tr->function_no_pids,
 						mutex_is_locked(&ftrace_lock));

-	if (trace_ignore_this_task(pid_list, no_pid_list, current))
+	if (trace_ignore_pid_task(pid_list, no_pid_list, current))
 		this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
 			       FTRACE_PID_IGNORE);
 	else
@@ -8981,7 +8981,7 @@ pid_write(struct file *filp, const char __user *ubuf,
 		trace_pid_list_free(filtered_pids);
 	} else if (pid_list && !other_pids) {
 		/* Register a probe to set whether to ignore the tracing of a task */
-		register_trace_sched_switch(ftrace_filter_pid_sched_switch_probe, tr);
+		register_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
 	}

 	/*
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 74a7a50d1e78..f73913eed307 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -845,7 +845,7 @@ extern struct workqueue_struct *trace_init_wq __initdata;

 bool trace_find_filtered_pid(struct trace_pid_list *filtered_pids,
 			     pid_t search_pid);
-bool trace_ignore_this_task(struct trace_pid_list *filtered_pids,
+bool trace_ignore_pid_task(struct trace_pid_list *filtered_pids,
 			    struct trace_pid_list *filtered_no_pids,
 			    struct task_struct *task);
 void trace_filter_add_remove_task(struct trace_pid_list *pid_list,
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 9f8f2d02276c..3fb0d2af5740 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1123,12 +1123,12 @@ event_filter_pid_sched_switch_probe_pre(void *data, bool preempt,
 	 * Sched switch is funny, as we only want to ignore it
 	 * in the notrace case if both prev and next should be ignored.
 	 */
-	ret = trace_ignore_this_task(NULL, no_pid_list, prev) &&
-		trace_ignore_this_task(NULL, no_pid_list, next);
+	ret = trace_ignore_pid_task(NULL, no_pid_list, prev) &&
+		trace_ignore_pid_task(NULL, no_pid_list, next);

 	this_cpu_write(tr->array_buffer.data->ignore_pid, ret ||
-		       (trace_ignore_this_task(pid_list, NULL, prev) &&
-			trace_ignore_this_task(pid_list, NULL, next)));
+		       (trace_ignore_pid_task(pid_list, NULL, prev) &&
+			trace_ignore_pid_task(pid_list, NULL, next)));
 }

 static void
@@ -1145,7 +1145,7 @@ event_filter_pid_sched_switch_probe_post(void *data, bool preempt,
 	no_pid_list = rcu_dereference_sched(tr->filtered_no_pids);

 	this_cpu_write(tr->array_buffer.data->ignore_pid,
-		       trace_ignore_this_task(pid_list, no_pid_list, next));
+		       trace_ignore_pid_task(pid_list, no_pid_list, next));
 }

 static void
@@ -1163,7 +1163,7 @@ event_filter_pid_sched_wakeup_probe_pre(void *data, struct task_struct *task)
 	no_pid_list = rcu_dereference_sched(tr->filtered_no_pids);

 	this_cpu_write(tr->array_buffer.data->ignore_pid,
-		       trace_ignore_this_task(pid_list, no_pid_list, task));
+		       trace_ignore_pid_task(pid_list, no_pid_list, task));
 }

 static void
@@ -1182,7 +1182,7 @@ event_filter_pid_sched_wakeup_probe_post(void *data, struct task_struct *task)

 	/* Set tracing if current is enabled */
 	this_cpu_write(tr->array_buffer.data->ignore_pid,
-		       trace_ignore_this_task(pid_list, no_pid_list, current));
+		       trace_ignore_pid_task(pid_list, no_pid_list, current));
 }

 static void unregister_pid_events(struct trace_array *tr)
@@ -2556,7 +2556,7 @@ static void ignore_task_cpu(void *data)
 					     mutex_is_locked(&event_mutex));

 	this_cpu_write(tr->array_buffer.data->ignore_pid,
-		       trace_ignore_this_task(pid_list, no_pid_list, current));
+		       trace_ignore_pid_task(pid_list, no_pid_list, current));
 }

 static void register_pid_events(struct trace_array *tr)
diff --git a/kernel/trace/trace_functions.c b/kernel/trace/trace_functions.c
index cd37f2013758..d569233df7c9 100644
--- a/kernel/trace/trace_functions.c
+++ b/kernel/trace/trace_functions.c
@@ -297,7 +297,7 @@ function_stack_trace_call(unsigned long ip, unsigned long parent_ip,
 		trace_ctx = tracing_gen_ctx_flags(flags);
 		trace_function(tr, ip, parent_ip, trace_ctx, NULL);
 #ifdef CONFIG_UNWINDER_FRAME_POINTER
-		if (ftrace_pids_enabled(op))
+		if (ftrace_task_filters_enabled(op))
 			skip++;
 #endif
 		__trace_stack(tr, trace_ctx, skip);
diff --git a/kernel/trace/trace_pid.c b/kernel/trace/trace_pid.c
index 7127c8de4174..ec9d3558b543 100644
--- a/kernel/trace/trace_pid.c
+++ b/kernel/trace/trace_pid.c
@@ -16,17 +16,17 @@ trace_find_filtered_pid(struct trace_pid_list *filtered_pids, pid_t search_pid)
 }

 /**
- * trace_ignore_this_task - should a task be ignored for tracing
+ * trace_ignore_pid_task - should a task be ignored by PID filters
  * @filtered_pids: The list of pids to check
  * @filtered_no_pids: The list of pids not to be traced
- * @task: The task that should be ignored if not filtered
+ * @task: The task to test against the PID filters
  *
- * Checks if @task should be traced or not from @filtered_pids.
+ * Checks whether @task should be ignored by the PID include/exclude filters.
  * Returns true if @task should *NOT* be traced.
  * Returns false if @task should be traced.
  */
 bool
-trace_ignore_this_task(struct trace_pid_list *filtered_pids,
+trace_ignore_pid_task(struct trace_pid_list *filtered_pids,
 		       struct trace_pid_list *filtered_no_pids,
 		       struct task_struct *task)
 {
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
  2026-08-30 11:09 ` [RFC PATCH 1/4] ftrace: Generalize function task filter names hu.shengming
@ 2026-08-30 11:10 ` hu.shengming
  2026-08-30 11:24   ` sashiko-bot
  2026-08-30 11:12 ` [RFC PATCH 3/4] ftrace: Add exact task comm filtering hu.shengming
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: hu.shengming @ 2026-08-30 11:10 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	rdunlap, linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

From: Shengming Hu <hu.shengming@zte.com.cn>

A later change will add task comm filtering alongside the existing PID
filters. Both filters need to share the sched_switch probe and the
per-CPU cached task decision.

Move the probe registration and cache refresh logic into
ftrace_task_filters_changed(). This gives PID and future task filters
a single place to update the shared state when a filter changes.

The helper also refreshes the cached result for currently running tasks
when one PID filter is cleared while the other remains active, instead
of leaving the result unchanged until the next schedule-in.

Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
---
 kernel/trace/ftrace.c | 58 +++++++++++++++++++++++++++----------------
 1 file changed, 37 insertions(+), 21 deletions(-)

diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 479ea004adc3..0c73abb8fec8 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -8637,6 +8637,16 @@ ftrace_func_t ftrace_ops_get_func(struct ftrace_ops *ops)
 	return ops->func;
 }

+static bool ftrace_task_filters_active(struct trace_array *tr)
+{
+	return rcu_dereference_protected(tr->function_pids,
+					 lockdep_is_held(&ftrace_lock)) ||
+		rcu_dereference_protected(tr->function_no_pids,
+					  lockdep_is_held(&ftrace_lock));
+}
+
+static void ignore_task_cpu(void *data);
+
 static void
 ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
 				     struct task_struct *prev,
@@ -8703,11 +8713,31 @@ void ftrace_pid_follow_fork(struct trace_array *tr, bool enable)
 	}
 }

+static void ftrace_task_filters_changed(struct trace_array *tr,
+					bool was_enabled)
+{
+	bool enabled = ftrace_task_filters_active(tr);
+	int cpu;
+
+	if (!was_enabled && enabled)
+		register_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
+	else if (was_enabled && !enabled) {
+		unregister_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
+		for_each_possible_cpu(cpu)
+			per_cpu_ptr(tr->array_buffer.data, cpu)->ftrace_ignore_pid =
+				FTRACE_PID_TRACE;
+		return;
+	}
+
+	if (enabled)
+		on_each_cpu(ignore_task_cpu, tr, 1);
+}
+
 static void clear_ftrace_pids(struct trace_array *tr, int type)
 {
 	struct trace_pid_list *pid_list;
 	struct trace_pid_list *no_pid_list;
-	int cpu;
+	bool task_filters_enabled;

 	pid_list = rcu_dereference_protected(tr->function_pids,
 					     lockdep_is_held(&ftrace_lock));
@@ -8718,12 +8748,7 @@ static void clear_ftrace_pids(struct trace_array *tr, int type)
 	if (!pid_type_enabled(type, pid_list, no_pid_list))
 		return;

-	/* See if the pids still need to be checked after this */
-	if (!still_need_pid_events(type, pid_list, no_pid_list)) {
-		unregister_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
-		for_each_possible_cpu(cpu)
-			per_cpu_ptr(tr->array_buffer.data, cpu)->ftrace_ignore_pid = FTRACE_PID_TRACE;
-	}
+	task_filters_enabled = ftrace_task_filters_active(tr);

 	if (type & TRACE_PIDS)
 		rcu_assign_pointer(tr->function_pids, NULL);
@@ -8731,6 +8756,8 @@ static void clear_ftrace_pids(struct trace_array *tr, int type)
 	if (type & TRACE_NO_PIDS)
 		rcu_assign_pointer(tr->function_no_pids, NULL);

+	ftrace_task_filters_changed(tr, task_filters_enabled);
+
 	/* Wait till all users are no longer using pid filtering */
 	synchronize_rcu();

@@ -8935,27 +8962,24 @@ pid_write(struct file *filp, const char __user *ubuf,
 	struct seq_file *m = filp->private_data;
 	struct trace_array *tr = m->private;
 	struct trace_pid_list *filtered_pids;
-	struct trace_pid_list *other_pids;
 	struct trace_pid_list *pid_list;
+	bool task_filters_enabled;
 	ssize_t ret;

 	if (!cnt)
 		return 0;

 	guard(mutex)(&ftrace_lock);
+	task_filters_enabled = ftrace_task_filters_active(tr);

 	switch (type) {
 	case TRACE_PIDS:
 		filtered_pids = rcu_dereference_protected(tr->function_pids,
 					     lockdep_is_held(&ftrace_lock));
-		other_pids = rcu_dereference_protected(tr->function_no_pids,
-					     lockdep_is_held(&ftrace_lock));
 		break;
 	case TRACE_NO_PIDS:
 		filtered_pids = rcu_dereference_protected(tr->function_no_pids,
 					     lockdep_is_held(&ftrace_lock));
-		other_pids = rcu_dereference_protected(tr->function_pids,
-					     lockdep_is_held(&ftrace_lock));
 		break;
 	default:
 		WARN_ON_ONCE(1);
@@ -8979,17 +9003,9 @@ pid_write(struct file *filp, const char __user *ubuf,
 	if (filtered_pids) {
 		synchronize_rcu();
 		trace_pid_list_free(filtered_pids);
-	} else if (pid_list && !other_pids) {
-		/* Register a probe to set whether to ignore the tracing of a task */
-		register_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
 	}

-	/*
-	 * Ignoring of pids is done at task switch. But we have to
-	 * check for those tasks that are currently running.
-	 * Always do this in case a pid was appended or removed.
-	 */
-	on_each_cpu(ignore_task_cpu, tr, 1);
+	ftrace_task_filters_changed(tr, task_filters_enabled);

 	ftrace_update_pid_func();
 	ftrace_startup_all(0);
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [RFC PATCH 3/4] ftrace: Add exact task comm filtering
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
  2026-08-30 11:09 ` [RFC PATCH 1/4] ftrace: Generalize function task filter names hu.shengming
  2026-08-30 11:10 ` ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates hu.shengming
@ 2026-08-30 11:12 ` hu.shengming
  2026-08-30 11:12 ` [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters hu.shengming
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: hu.shengming @ 2026-08-30 11:12 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	rdunlap, linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

From: Shengming Hu <hu.shengming@zte.com.cn>

PID filters only select tasks that already have a known PID. This makes
it difficult to set up function tracing for a service before it starts
or to keep tracing it after it restarts with a different PID.

Add set_ftrace_comm and set_ftrace_notrace_comm to filter function and
function_graph tracing by task comm.

Comm filters share the existing sched_switch probe and per-CPU cached
task decision with PID filters. The filters are checked when a task is
scheduled in, so the function tracing fast path remains unchanged.

Comm names are matched exactly. Each write adds one name to the list.
A trailing newline is ignored, while embedded newlines and names longer
than TASK_COMM_LEN - 1 are rejected. Empty writes have no effect,
duplicate names are ignored, and opening the file with O_TRUNC clears
the list.

When both PID and comm include filters are set, a task must match both.
A match in either exclude filter prevents the task from being traced.
If a running task changes its comm, the new name takes effect the next
time the task is scheduled in.

Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
---
 kernel/trace/Makefile    |   1 +
 kernel/trace/comm_list.c | 314 ++++++++++++++++++++++++++++++++++++
 kernel/trace/comm_list.h |  17 ++
 kernel/trace/ftrace.c    | 336 ++++++++++++++++++++++++++++++++++++++-
 kernel/trace/trace.c     |   5 +
 kernel/trace/trace.h     |  32 ++++
 6 files changed, 701 insertions(+), 4 deletions(-)
 create mode 100644 kernel/trace/comm_list.c
 create mode 100644 kernel/trace/comm_list.h

diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
index f934ff586bd4..415cd9db9c3e 100644
--- a/kernel/trace/Makefile
+++ b/kernel/trace/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_TRACING) += trace_printk.o
 obj-$(CONFIG_TRACING) += trace_pid.o
 obj-$(CONFIG_TRACER_SNAPSHOT) += trace_snapshot.o
 obj-$(CONFIG_TRACING) += 	pid_list.o
+obj-$(CONFIG_TRACING) +=    comm_list.o
 obj-$(CONFIG_TRACING_MAP) += tracing_map.o
 obj-$(CONFIG_PREEMPTIRQ_DELAY_TEST) += preemptirq_delay_test.o
 obj-$(CONFIG_SYNTH_EVENT_GEN_TEST) += synth_event_gen_test.o
diff --git a/kernel/trace/comm_list.c b/kernel/trace/comm_list.c
new file mode 100644
index 000000000000..c94c53d7d70c
--- /dev/null
+++ b/kernel/trace/comm_list.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 ZTE Inc, Shengming Hu <hu.shengming@zte.com.cn>
+ */
+
+#include <linux/limits.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "trace.h"
+#include "comm_list.h"
+
+#define COMM_LIST_INIT_SIZE	4
+
+static int comm_cmp(const char a[TASK_COMM_LEN],
+		    const char b[TASK_COMM_LEN])
+{
+	return memcmp(a, b, TASK_COMM_LEN);
+}
+
+/*
+ * Return true if @comm exists. @pos is either the matching position or the
+ * insertion position that keeps the array sorted.
+ */
+static bool trace_comm_list_find(struct trace_comm_list *comm_list,
+				 const char comm[TASK_COMM_LEN],
+				 unsigned int *pos)
+{
+	unsigned int low = 0;
+	unsigned int high;
+
+	if (!comm_list) {
+		*pos = 0;
+		return false;
+	}
+
+	high = comm_list->nr_comms;
+	while (low < high) {
+		unsigned int mid = low + (high - low) / 2;
+		int cmp = comm_cmp(comm_list->comms[mid], comm);
+
+		if (cmp < 0)
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	*pos = low;
+	return low < comm_list->nr_comms &&
+	       !comm_cmp(comm_list->comms[low], comm);
+}
+
+/**
+ * trace_comm_list_alloc - create a new comm_list
+ *
+ * Allocates a new comm_list to store comms into.
+ *
+ * Returns the comm_list on success, NULL otherwise.
+ */
+struct trace_comm_list *trace_comm_list_alloc(void)
+{
+	return kzalloc(sizeof(struct trace_comm_list), GFP_KERNEL);
+}
+
+/**
+ * trace_comm_list_free - Frees an allocated comm_list.
+ * @comm_list: The comm list to free.
+ *
+ * Frees the memory for a comm_list that was allocated.
+ */
+void trace_comm_list_free(struct trace_comm_list *comm_list)
+{
+	if (!comm_list)
+		return;
+
+	kfree(comm_list->comms);
+	kfree(comm_list);
+}
+
+/**
+ * trace_comm_list_is_set - test if the comm is set in the list
+ * @comm_list: The comm list to test
+ * @comm: The comm to see if set in the list.
+ *
+ * Tests if @comm is set in the @comm_list.
+ *
+ * Return true if the comm is in the list, false otherwise.
+ */
+bool trace_comm_list_is_set(struct trace_comm_list *comm_list,
+			    const char comm[TASK_COMM_LEN])
+{
+	unsigned int pos;
+
+	return trace_comm_list_find(comm_list, comm, &pos);
+}
+
+static int trace_comm_list_grow(struct trace_comm_list *comm_list)
+{
+	char (*comms)[TASK_COMM_LEN];
+	unsigned int max_comms;
+
+	if (comm_list->nr_comms < comm_list->max_comms)
+		return 0;
+
+	if (!comm_list->max_comms) {
+		max_comms = COMM_LIST_INIT_SIZE;
+	} else {
+		if (comm_list->max_comms > UINT_MAX / 2)
+			return -E2BIG;
+		max_comms = comm_list->max_comms * 2;
+	}
+
+	comms = krealloc_array(comm_list->comms, max_comms,
+			       sizeof(*comm_list->comms), GFP_KERNEL);
+	if (!comms)
+		return -ENOMEM;
+
+	comm_list->comms = comms;
+	comm_list->max_comms = max_comms;
+	return 0;
+}
+
+/**
+ * trace_comm_list_set - add a comm to the list
+ * @comm_list: The comm list to add the @comm to.
+ * @comm: The comm to add.
+ *
+ * Adds @comm to @comm_list. The comms are kept sorted and duplicate
+ * entries are ignored.
+ *
+ * Return 0 on success, negative otherwise.
+ */
+int trace_comm_list_set(struct trace_comm_list *comm_list,
+			const char comm[TASK_COMM_LEN])
+{
+	unsigned int pos;
+	int ret;
+
+	if (!comm_list)
+		return -ENODEV;
+
+	if (trace_comm_list_find(comm_list, comm, &pos))
+		return 0;
+
+	ret = trace_comm_list_grow(comm_list);
+	if (ret)
+		return ret;
+
+	if (pos < comm_list->nr_comms)
+		memmove(&comm_list->comms[pos + 1], &comm_list->comms[pos],
+			(comm_list->nr_comms - pos) * sizeof(*comm_list->comms));
+
+	memcpy(comm_list->comms[pos], comm, TASK_COMM_LEN);
+	comm_list->nr_comms++;
+
+	return 0;
+}
+
+/**
+ * trace_ignore_comm_task - should a task be ignored by comm filters
+ * @filtered_comms: The list of comms to trace
+ * @filtered_no_comms: The list of comms not to be traced
+ * @task: The task to test against the comm filters
+ *
+ * Checks whether @task should be ignored by the comm include/exclude
+ * filters.
+ *
+ * Returns true if @task should not be traced, false otherwise.
+ */
+bool trace_ignore_comm_task(struct trace_comm_list *filtered_comms,
+			    struct trace_comm_list *filtered_no_comms,
+			    struct task_struct *task)
+{
+	char comm[TASK_COMM_LEN];
+
+	get_task_comm(comm, task);
+
+	return (filtered_comms &&
+		!trace_comm_list_is_set(filtered_comms, comm)) ||
+	       (filtered_no_comms &&
+		trace_comm_list_is_set(filtered_no_comms, comm));
+}
+
+/**
+ * trace_comm_start - start iterating over a comm list
+ * @comm_list: The comm list to show
+ * @pos: The position of the file
+ *
+ * Returns the comm at @pos, or NULL if there are no more comms.
+ */
+void *trace_comm_start(struct trace_comm_list *comm_list, loff_t *pos)
+{
+	if (!comm_list || *pos < 0 || *pos >= (loff_t)comm_list->nr_comms)
+		return NULL;
+
+	return comm_list->comms[*pos];
+}
+
+/**
+ * trace_comm_next - return the next comm in the list
+ * @comm_list: The comm list to show
+ * @v: The current comm
+ * @pos: The position of the file
+ *
+ * Returns the next comm in @comm_list, or NULL if there are no more comms.
+ */
+void *trace_comm_next(struct trace_comm_list *comm_list, void *v, loff_t *pos)
+{
+	(void)v;
+
+	(*pos)++;
+	return trace_comm_start(comm_list, pos);
+}
+
+/**
+ * trace_comm_show - show the current comm
+ * @m: The seq_file structure to write into
+ * @v: The comm to display
+ *
+ * Displays the current comm in the seq_file.
+ */
+int trace_comm_show(struct seq_file *m, void *v)
+{
+	seq_printf(m, "%s\n", (char *)v);
+	return 0;
+}
+
+static int trace_comm_from_user(char comm[TASK_COMM_LEN],
+				const char __user *ubuf, size_t cnt)
+{
+	char buf[TASK_COMM_LEN];
+	size_t len = cnt;
+
+	if (cnt > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, ubuf, cnt))
+		return -EFAULT;
+
+	if (len && buf[len - 1] == '\n')
+		len--;
+
+	if (!len) {
+		comm[0] = '\0';
+		return 0;
+	}
+
+	if (len >= TASK_COMM_LEN || memchr(buf, '\0', len) ||
+	    memchr(buf, '\n', len))
+		return -EINVAL;
+
+	memset(comm, 0, TASK_COMM_LEN);
+	memcpy(comm, buf, len);
+
+	return 1;
+}
+
+/**
+ * trace_comm_write - add a comm to a comm list
+ * @filtered_comms: The current comm list
+ * @new_comm_list: The pointer to place the new comm list
+ * @ubuf: The user buffer containing the comm
+ * @cnt: The size of the user buffer
+ *
+ * Creates a new comm list containing the current comms and the comm
+ * specified by the user.
+ *
+ * Return the number of bytes written on success, negative otherwise.
+ */
+int trace_comm_write(struct trace_comm_list *filtered_comms,
+		     struct trace_comm_list **new_comm_list,
+		     const char __user *ubuf, size_t cnt)
+{
+	struct trace_comm_list *comm_list;
+	char comm[TASK_COMM_LEN];
+	unsigned int i;
+	int parsed;
+	int ret;
+
+	parsed = trace_comm_from_user(comm, ubuf, cnt);
+	if (parsed < 0)
+		return parsed;
+
+	if (!parsed || trace_comm_list_is_set(filtered_comms, comm)) {
+		*new_comm_list = filtered_comms;
+		return cnt;
+	}
+
+	comm_list = trace_comm_list_alloc();
+	if (!comm_list)
+		return -ENOMEM;
+
+	if (filtered_comms) {
+		for (i = 0; i < filtered_comms->nr_comms; i++) {
+			ret = trace_comm_list_set(comm_list,
+						  filtered_comms->comms[i]);
+			if (ret < 0)
+				goto fail;
+		}
+	}
+
+	ret = trace_comm_list_set(comm_list, comm);
+	if (ret < 0)
+		goto fail;
+
+	*new_comm_list = comm_list;
+	return cnt;
+
+fail:
+	trace_comm_list_free(comm_list);
+	return ret;
+}
diff --git a/kernel/trace/comm_list.h b/kernel/trace/comm_list.h
new file mode 100644
index 000000000000..1f2a6b329ed3
--- /dev/null
+++ b/kernel/trace/comm_list.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* Do not include this file directly. */
+
+#ifndef _TRACE_INTERNAL_COMM_LIST_H
+#define _TRACE_INTERNAL_COMM_LIST_H
+
+#include <linux/sched.h>
+
+struct trace_comm_list {
+	unsigned int nr_comms;
+	unsigned int max_comms;
+	char (*comms)[TASK_COMM_LEN];
+};
+
+#endif /* _TRACE_INTERNAL_COMM_LIST_H */
+
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 0c73abb8fec8..e2a466448a9b 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -108,7 +108,10 @@ bool ftrace_task_filters_enabled(struct ftrace_ops *ops)

 	tr = ops->private;

-	return tr->function_pids != NULL || tr->function_no_pids != NULL;
+	return rcu_access_pointer(tr->function_pids) ||
+		rcu_access_pointer(tr->function_no_pids) ||
+		rcu_access_pointer(tr->function_comms) ||
+		rcu_access_pointer(tr->function_no_comms);
 }

 static void ftrace_update_trampoline(struct ftrace_ops *ops);
@@ -168,7 +171,7 @@ static inline void ftrace_ops_init(struct ftrace_ops *ops)
 #endif
 }

-/* Call this function for when a callback filters on set_ftrace_pid */
+/* Call this function for when a callback uses task filters */
 static void ftrace_pid_func(unsigned long ip, unsigned long parent_ip,
 			    struct ftrace_ops *op, struct ftrace_regs *fregs)
 {
@@ -8642,11 +8645,32 @@ static bool ftrace_task_filters_active(struct trace_array *tr)
 	return rcu_dereference_protected(tr->function_pids,
 					 lockdep_is_held(&ftrace_lock)) ||
 		rcu_dereference_protected(tr->function_no_pids,
+					  lockdep_is_held(&ftrace_lock)) ||
+		rcu_dereference_protected(tr->function_comms,
+					  lockdep_is_held(&ftrace_lock)) ||
+		rcu_dereference_protected(tr->function_no_comms,
 					  lockdep_is_held(&ftrace_lock));
 }

 static void ignore_task_cpu(void *data);

+static bool __ftrace_ignore_task(struct trace_pid_list *pid_list,
+				 struct trace_pid_list *no_pid_list,
+				 struct trace_comm_list *comm_filter,
+				 struct trace_comm_list *no_comm_filter,
+				 struct task_struct *task)
+{
+	if ((pid_list || no_pid_list) &&
+	    trace_ignore_pid_task(pid_list, no_pid_list, task))
+		return true;
+
+	if ((comm_filter || no_comm_filter) &&
+	    trace_ignore_comm_task(comm_filter, no_comm_filter, task))
+		return true;
+
+	return false;
+}
+
 static void
 ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
 				     struct task_struct *prev,
@@ -8656,11 +8680,15 @@ ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
 	struct trace_array *tr = data;
 	struct trace_pid_list *pid_list;
 	struct trace_pid_list *no_pid_list;
+	struct trace_comm_list *comm_filter;
+	struct trace_comm_list *no_comm_filter;

 	pid_list = rcu_dereference_sched(tr->function_pids);
 	no_pid_list = rcu_dereference_sched(tr->function_no_pids);

-	if (trace_ignore_pid_task(pid_list, no_pid_list, next))
+	comm_filter = rcu_dereference_sched(tr->function_comms);
+	no_comm_filter = rcu_dereference_sched(tr->function_no_comms);
+	if (__ftrace_ignore_task(pid_list, no_pid_list, comm_filter, no_comm_filter, next))
 		this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
 			       FTRACE_PID_IGNORE);
 	else
@@ -8788,6 +8816,54 @@ static void ftrace_pid_reset(struct trace_array *tr, int type)
 	mutex_unlock(&ftrace_lock);
 }

+static void clear_ftrace_comms(struct trace_array *tr, int type)
+{
+	struct trace_comm_list *comm_filter;
+	struct trace_comm_list *no_comm_filter;
+	bool task_filters_enabled;
+
+	comm_filter = rcu_dereference_protected(tr->function_comms,
+						lockdep_is_held(&ftrace_lock));
+	no_comm_filter = rcu_dereference_protected(tr->function_no_comms,
+						   lockdep_is_held(&ftrace_lock));
+
+	if (!comm_type_enabled(type, comm_filter, no_comm_filter))
+		return;
+
+	task_filters_enabled = ftrace_task_filters_active(tr);
+
+	if (type & TRACE_COMMS)
+		rcu_assign_pointer(tr->function_comms, NULL);
+
+	if (type & TRACE_NO_COMMS)
+		rcu_assign_pointer(tr->function_no_comms, NULL);
+
+	ftrace_task_filters_changed(tr, task_filters_enabled);
+	synchronize_rcu();
+
+	if ((type & TRACE_COMMS) && comm_filter)
+		trace_comm_list_free(comm_filter);
+
+	if ((type & TRACE_NO_COMMS) && no_comm_filter)
+		trace_comm_list_free(no_comm_filter);
+}
+
+void ftrace_clear_comms(struct trace_array *tr)
+{
+	mutex_lock(&ftrace_lock);
+	clear_ftrace_comms(tr, TRACE_COMMS | TRACE_NO_COMMS);
+	mutex_unlock(&ftrace_lock);
+}
+
+static void ftrace_comm_reset(struct trace_array *tr, int type)
+{
+	mutex_lock(&ftrace_lock);
+	clear_ftrace_comms(tr, type);
+	ftrace_update_pid_func();
+	ftrace_startup_all(0);
+	mutex_unlock(&ftrace_lock);
+}
+
 /* Greater than any max PID */
 #define FTRACE_NO_PIDS		(void *)(PID_MAX_LIMIT + 1)

@@ -8880,6 +8956,102 @@ static const struct seq_operations ftrace_no_pid_sops = {
 	.show = fpid_show,
 };

+/* Not a valid comm pointer */
+#define FTRACE_NO_COMM		((void *)1)
+
+static void *fcomm_start(struct seq_file *m, loff_t *pos)
+	__acquires(RCU)
+{
+	struct trace_comm_list *comm_list;
+	struct trace_array *tr = m->private;
+
+	mutex_lock(&ftrace_lock);
+	rcu_read_lock_sched();
+
+	comm_list = rcu_dereference_sched(tr->function_comms);
+
+	if (!comm_list)
+		return !(*pos) ? FTRACE_NO_COMM : NULL;
+
+	return trace_comm_start(comm_list, pos);
+}
+
+static void *fcomm_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct trace_array *tr = m->private;
+	struct trace_comm_list *comm_list;
+
+	if (v == FTRACE_NO_COMM) {
+		(*pos)++;
+		return NULL;
+	}
+
+	comm_list = rcu_dereference_sched(tr->function_comms);
+	return trace_comm_next(comm_list, v, pos);
+}
+
+static void fcomm_stop(struct seq_file *m, void *p)
+	__releases(RCU)
+{
+	rcu_read_unlock_sched();
+	mutex_unlock(&ftrace_lock);
+}
+
+static int fcomm_show(struct seq_file *m, void *v)
+{
+	if (v == FTRACE_NO_COMM) {
+		seq_puts(m, "no comm\n");
+		return 0;
+	}
+
+	return trace_comm_show(m, v);
+}
+
+static const struct seq_operations ftrace_comm_sops = {
+	.start	= fcomm_start,
+	.next	= fcomm_next,
+	.stop	= fcomm_stop,
+	.show	= fcomm_show,
+};
+
+static void *fncomm_start(struct seq_file *m, loff_t *pos)
+	__acquires(RCU)
+{
+	struct trace_comm_list *comm_list;
+	struct trace_array *tr = m->private;
+
+	mutex_lock(&ftrace_lock);
+	rcu_read_lock_sched();
+
+	comm_list = rcu_dereference_sched(tr->function_no_comms);
+
+	if (!comm_list)
+		return !(*pos) ? FTRACE_NO_COMM : NULL;
+
+	return trace_comm_start(comm_list, pos);
+}
+
+static void *fncomm_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct trace_array *tr = m->private;
+	struct trace_comm_list *comm_list;
+
+	if (v == FTRACE_NO_COMM) {
+		(*pos)++;
+		return NULL;
+	}
+
+	comm_list = rcu_dereference_sched(tr->function_no_comms);
+	return trace_comm_next(comm_list, v, pos);
+}
+
+static const struct seq_operations ftrace_no_comm_sops = {
+	.start	= fncomm_start,
+	.next	= fncomm_next,
+	.stop	= fcomm_stop,
+	.show	= fcomm_show,
+};
+
 static int pid_open(struct inode *inode, struct file *file, int type)
 {
 	const struct seq_operations *seq_ops;
@@ -8932,11 +9104,62 @@ ftrace_no_pid_open(struct inode *inode, struct file *file)
 	return pid_open(inode, file, TRACE_NO_PIDS);
 }

+static int comm_open(struct inode *inode, struct file *file, int type)
+{
+	const struct seq_operations *seq_ops;
+	struct trace_array *tr = inode->i_private;
+	struct seq_file *m;
+	int ret = 0;
+
+	ret = tracing_check_open_get_tr(tr);
+	if (ret)
+		return ret;
+
+	if ((file->f_mode & FMODE_WRITE) &&
+	    (file->f_flags & O_TRUNC))
+		ftrace_comm_reset(tr, type);
+
+	switch (type) {
+	case TRACE_COMMS:
+		seq_ops = &ftrace_comm_sops;
+		break;
+	case TRACE_NO_COMMS:
+		seq_ops = &ftrace_no_comm_sops;
+		break;
+	default:
+		trace_array_put(tr);
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+
+	ret = seq_open(file, seq_ops);
+	if (ret < 0) {
+		trace_array_put(tr);
+	} else {
+		m = file->private_data;
+		m->private = tr;
+	}
+
+	return ret;
+}
+
+static int ftrace_comm_open(struct inode *inode, struct file *file)
+{
+	return comm_open(inode, file, TRACE_COMMS);
+}
+
+static int ftrace_no_comm_open(struct inode *inode, struct file *file)
+{
+	return comm_open(inode, file, TRACE_NO_COMMS);
+}
+
 static void ignore_task_cpu(void *data)
 {
 	struct trace_array *tr = data;
 	struct trace_pid_list *pid_list;
 	struct trace_pid_list *no_pid_list;
+	struct trace_comm_list *comm_filter;
+	struct trace_comm_list *no_comm_filter;

 	/*
 	 * This function is called by on_each_cpu() while the
@@ -8947,7 +9170,12 @@ static void ignore_task_cpu(void *data)
 	no_pid_list = rcu_dereference_protected(tr->function_no_pids,
 						mutex_is_locked(&ftrace_lock));

-	if (trace_ignore_pid_task(pid_list, no_pid_list, current))
+	comm_filter = rcu_dereference_protected(tr->function_comms,
+						mutex_is_locked(&ftrace_lock));
+	no_comm_filter = rcu_dereference_protected(tr->function_no_comms,
+						   mutex_is_locked(&ftrace_lock));
+
+	if (__ftrace_ignore_task(pid_list, no_pid_list, comm_filter, no_comm_filter, current))
 		this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
 			       FTRACE_PID_IGNORE);
 	else
@@ -9022,6 +9250,86 @@ ftrace_pid_write(struct file *filp, const char __user *ubuf,
 	return pid_write(filp, ubuf, cnt, ppos, TRACE_PIDS);
 }

+static ssize_t comm_write(struct file *filp, const char __user *ubuf,
+			  size_t cnt, loff_t *ppos, int type)
+{
+	struct seq_file *m = filp->private_data;
+	struct trace_array *tr = m->private;
+	struct trace_comm_list *filtered_comms;
+	struct trace_comm_list *comm_list;
+	bool task_filters_enabled;
+	ssize_t ret;
+
+	if (!cnt)
+		return 0;
+
+	guard(mutex)(&ftrace_lock);
+	task_filters_enabled = ftrace_task_filters_active(tr);
+
+	switch (type) {
+	case TRACE_COMMS:
+		filtered_comms = rcu_dereference_protected(tr->function_comms,
+						      lockdep_is_held(&ftrace_lock));
+		break;
+	case TRACE_NO_COMMS:
+		filtered_comms = rcu_dereference_protected(tr->function_no_comms,
+						      lockdep_is_held(&ftrace_lock));
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+
+	ret = trace_comm_write(filtered_comms, &comm_list, ubuf, cnt);
+	if (ret < 0)
+		return ret;
+
+	if (comm_list == filtered_comms) {
+		*ppos += ret;
+		return ret;
+	}
+
+	if (type == TRACE_COMMS)
+		rcu_assign_pointer(tr->function_comms, comm_list);
+	else
+		rcu_assign_pointer(tr->function_no_comms, comm_list);
+
+	if (filtered_comms) {
+		synchronize_rcu();
+		trace_comm_list_free(filtered_comms);
+	}
+
+	ftrace_task_filters_changed(tr, task_filters_enabled);
+	ftrace_update_pid_func();
+	ftrace_startup_all(0);
+
+	*ppos += ret;
+	return ret;
+}
+
+static ssize_t ftrace_comm_write(struct file *filp, const char __user *ubuf,
+				 size_t cnt, loff_t *ppos)
+{
+	return comm_write(filp, ubuf, cnt, ppos, TRACE_COMMS);
+}
+
+static ssize_t ftrace_no_comm_write(struct file *filp,
+				    const char __user *ubuf,
+				    size_t cnt, loff_t *ppos)
+{
+	return comm_write(filp, ubuf, cnt, ppos, TRACE_NO_COMMS);
+}
+
+static int
+ftrace_comm_release(struct inode *inode, struct file *file)
+{
+	struct trace_array *tr = inode->i_private;
+
+	trace_array_put(tr);
+
+	return seq_release(inode, file);
+}
+
 static ssize_t
 ftrace_no_pid_write(struct file *filp, const char __user *ubuf,
 		    size_t cnt, loff_t *ppos)
@@ -9055,12 +9363,32 @@ static const struct file_operations ftrace_no_pid_fops = {
 	.release	= ftrace_pid_release,
 };

+static const struct file_operations ftrace_comm_fops = {
+	.open		= ftrace_comm_open,
+	.write		= ftrace_comm_write,
+	.read		= seq_read,
+	.llseek		= tracing_lseek,
+	.release	= ftrace_comm_release,
+};
+
+static const struct file_operations ftrace_no_comm_fops = {
+	.open		= ftrace_no_comm_open,
+	.write		= ftrace_no_comm_write,
+	.read		= seq_read,
+	.llseek		= tracing_lseek,
+	.release	= ftrace_comm_release,
+};
+
 void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 {
 	trace_create_file("set_ftrace_pid", TRACE_MODE_WRITE, d_tracer,
 			    tr, &ftrace_pid_fops);
 	trace_create_file("set_ftrace_notrace_pid", TRACE_MODE_WRITE,
 			  d_tracer, tr, &ftrace_no_pid_fops);
+	trace_create_file("set_ftrace_comm", TRACE_MODE_WRITE, d_tracer,
+			  tr, &ftrace_comm_fops);
+	trace_create_file("set_ftrace_notrace_comm", TRACE_MODE_WRITE,
+			  d_tracer, tr, &ftrace_no_comm_fops);
 }

 void __init ftrace_init_tracefs_toplevel(struct trace_array *tr,
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 3e0907aef172..8cbfaed24810 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4261,6 +4261,10 @@ static const char readme_msg[] =
 	"\t\t    (function)\n"
 	"  set_ftrace_notrace_pid\t- Write pid(s) to not function trace those pids\n"
 	"\t\t    (function)\n"
+	"  set_ftrace_comm\t- Write task comms to only function trace those tasks\n"
+	"\t\t    (function)\n"
+	"  set_ftrace_notrace_comm\t- Write task comms to not function trace those tasks\n"
+	"\t\t    (function)\n"
 #endif
 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
 	"  set_graph_function\t- Trace the nested calls of a function (function_graph)\n"
@@ -8789,6 +8793,7 @@ static int __remove_instance(struct trace_array *tr)
 	clear_ftrace_function_probes(tr);
 	event_trace_del_tracer(tr);
 	ftrace_clear_pids(tr);
+	ftrace_clear_comms(tr);
 	ftrace_destroy_function_files(tr);
 	tracefs_remove(tr->dir);
 	free_percpu(tr->last_func_repeats);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index f73913eed307..218bd26dc73a 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -182,6 +182,7 @@ struct fexit_trace_entry_head {
 #define TRACE_BUF_SIZE		1024

 struct trace_array;
+struct trace_comm_list;

 /*
  * The CPU trace array - it consists of thousands of trace entries
@@ -237,12 +238,32 @@ int trace_pid_list_clear(struct trace_pid_list *pid_list, unsigned int pid);
 int trace_pid_list_first(struct trace_pid_list *pid_list, unsigned int *pid);
 int trace_pid_list_next(struct trace_pid_list *pid_list, unsigned int pid,
 			unsigned int *next);
+struct trace_comm_list *trace_comm_list_alloc(void);
+void trace_comm_list_free(struct trace_comm_list *comm_list);
+bool trace_comm_list_is_set(struct trace_comm_list *comm_list,
+			    const char comm[TASK_COMM_LEN]);
+int trace_comm_list_set(struct trace_comm_list *comm_list,
+			const char comm[TASK_COMM_LEN]);
+bool trace_ignore_comm_task(struct trace_comm_list *filtered_comms,
+			    struct trace_comm_list *filtered_no_comms,
+			    struct task_struct *task);
+void *trace_comm_next(struct trace_comm_list *comm_list, void *v, loff_t *pos);
+void *trace_comm_start(struct trace_comm_list *comm_list, loff_t *pos);
+int trace_comm_show(struct seq_file *m, void *v);
+int trace_comm_write(struct trace_comm_list *filtered_comms,
+		     struct trace_comm_list **new_comm_list,
+		     const char __user *ubuf, size_t cnt);

 enum {
 	TRACE_PIDS		= BIT(0),
 	TRACE_NO_PIDS		= BIT(1),
 };

+enum {
+	TRACE_COMMS = BIT(0),
+	TRACE_NO_COMMS = BIT(1),
+};
+
 static inline bool pid_type_enabled(int type, struct trace_pid_list *pid_list,
 				    struct trace_pid_list *no_pid_list)
 {
@@ -251,6 +272,13 @@ static inline bool pid_type_enabled(int type, struct trace_pid_list *pid_list,
 		((type & TRACE_NO_PIDS) && no_pid_list);
 }

+static inline bool comm_type_enabled(int type, struct trace_comm_list *comm_list,
+			     struct trace_comm_list *no_comm_list)
+{
+	return ((type & TRACE_COMMS) && comm_list) ||
+		((type & TRACE_NO_COMMS) && no_comm_list);
+}
+
 static inline bool still_need_pid_events(int type, struct trace_pid_list *pid_list,
 					 struct trace_pid_list *no_pid_list)
 {
@@ -434,6 +462,8 @@ struct trace_array {
 	struct ftrace_ops	*ops;
 	struct trace_pid_list	__rcu *function_pids;
 	struct trace_pid_list	__rcu *function_no_pids;
+	struct trace_comm_list	__rcu *function_comms;
+	struct trace_comm_list	__rcu *function_no_comms;
 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
 	struct fgraph_ops	*gops;
 #endif
@@ -1267,6 +1297,7 @@ void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d_tracer);
 void ftrace_init_tracefs_toplevel(struct trace_array *tr,
 				  struct dentry *d_tracer);
 void ftrace_clear_pids(struct trace_array *tr);
+void ftrace_clear_comms(struct trace_array *tr);
 int init_function_trace(void);
 void ftrace_pid_follow_fork(struct trace_array *tr, bool enable);
 #else
@@ -1289,6 +1320,7 @@ static inline void ftrace_reset_array_ops(struct trace_array *tr) { }
 static inline void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d) { }
 static inline void ftrace_init_tracefs_toplevel(struct trace_array *tr, struct dentry *d) { }
 static inline void ftrace_clear_pids(struct trace_array *tr) { }
+static inline void ftrace_clear_comms(struct trace_array *tr) { }
 static inline int init_function_trace(void) { return 0; }
 static inline void ftrace_pid_follow_fork(struct trace_array *tr, bool enable) { }
 /* ftace_func_t type is not defined, use macro instead of static inline */
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
                   ` (2 preceding siblings ...)
  2026-08-30 11:12 ` [RFC PATCH 3/4] ftrace: Add exact task comm filtering hu.shengming
@ 2026-08-30 11:12 ` hu.shengming
  2026-08-30 15:27   ` Randy Dunlap
  2026-08-31  1:20 ` [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing Masami Hiramatsu
  2026-08-31 13:49 ` Steven Rostedt
  5 siblings, 1 reply; 12+ messages in thread
From: hu.shengming @ 2026-08-30 11:12 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	rdunlap, linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

From: Shengming Hu <hu.shengming@zte.com.cn>

Document set_ftrace_comm and set_ftrace_notrace_comm, including their
write and O_TRUNC behavior, accepted comm names, interaction with PID
filters, and when a task is rechecked after its comm changes.

Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
---
 Documentation/trace/ftrace.rst | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/Documentation/trace/ftrace.rst b/Documentation/trace/ftrace.rst
index 7261f25f8b4b..a96fc436ec16 100644
--- a/Documentation/trace/ftrace.rst
+++ b/Documentation/trace/ftrace.rst
@@ -313,6 +313,37 @@ of ftrace. Here is a list of some of the key files:
         If a PID is in both this file and "set_ftrace_pid", then this
         file takes precedence, and the thread will not be traced.

+  set_ftrace_comm:
+
+	Have the function and function_graph tracers trace only threads whose
+	task comm is listed in this file. Names are matched exactly and must
+	contain between 1 and TASK_COMM_LEN - 1 bytes.
+
+	Each write adds one name. A single trailing newline is ignored, while
+	embedded newlines are rejected. A zero-length write or a write containing
+	only a newline adds no entry. Duplicate entries have no effect.
+
+	Opening the file with O_TRUNC clears the filter. Otherwise, writes add
+	names to the existing filter. From a shell, use >> to add a name without
+	clearing the filter. Reading an empty filter prints `no comm`.
+
+	If both set_ftrace_pid and set_ftrace_comm contain entries, a thread must
+	match both filters to be traced.
+
+  set_ftrace_notrace_comm:
+
+	Have the function and function_graph tracers ignore threads whose task
+	comm is listed in this file. Matching and file operations have the same
+	semantics as set_ftrace_comm.
+
+	A match in either set_ftrace_notrace_pid or set_ftrace_notrace_comm
+	excludes the thread and takes precedence over all include filters.
+
+	Function-tracer task filters are evaluated when a task is scheduled in,
+	and the result is cached per CPU. Changing a filter refreshes the result
+	for currently running tasks. If a running task changes its comm, it is
+	rechecked the next time it is scheduled in.
+
   set_event_pid:

 	Have the events only trace a task with a PID listed in this file.
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates
  2026-08-30 11:10 ` ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates hu.shengming
@ 2026-08-30 11:24   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-08-30 11:24 UTC (permalink / raw)
  To: hu.shengming; +Cc: linux-trace-kernel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but unregister_trace_sched_switch does not wait for in-flight probes to complete before ftrace_ignore_pid is reset, permanently disabling tracing for a CPU.
--

commit 03992a852b272ad537b862229061fb0d87062720
Author: Shengming Hu <hu.shengming@zte.com.cn>

[RFC PATCH 2/4] ftrace: Centralize task filter state updates

This commit moves probe registration and cache refresh logic into a centralized 
helper ftrace_task_filters_changed() to prepare for task comm filtering. It 
updates shared state when a filter changes and refreshes cached results for 
running tasks when PID filters are cleared.

> diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
> index 825d4474876cc..05775047837d2 100644
> --- a/kernel/trace/ftrace.c
> +++ b/kernel/trace/ftrace.c

[ ... ]

> @@ -8677,11 +8687,31 @@ void ftrace_pid_follow_fork(struct trace_array *tr, bool enable)
>  	}
>  }
>  
> +static void ftrace_task_filters_changed(struct trace_array *tr,
> +					bool was_enabled)
> +{
> +	bool enabled = ftrace_task_filters_active(tr);
> +	int cpu;
> +
> +	if (!was_enabled && enabled)
> +		register_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
> +	else if (was_enabled && !enabled) {
> +		unregister_trace_sched_switch(ftrace_filter_task_sched_switch_probe, tr);
> +		for_each_possible_cpu(cpu)
> +			per_cpu_ptr(tr->array_buffer.data, cpu)->ftrace_ignore_pid =
> +				FTRACE_PID_TRACE;
> +		return;
> +	}

[Severity: High]
This is a pre-existing issue, but does unregister_trace_sched_switch wait for 
in-flight probes to complete before ftrace_ignore_pid is reset in the loop 
here?

If a concurrent sched_switch probe evaluates the old PID list and decides to 
ignore a task, while another CPU clears the filters, unregisters the probe, 
and resets ftrace_ignore_pid, we might have a race condition. If the in-flight 
probe writes FTRACE_PID_IGNORE after this reset loop completes, the state 
becomes corrupted and could permanently disable tracing for a CPU.

Can we safely reset ftrace_ignore_pid here without an RCU synchronization 
barrier first?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260830190743637MhXoixrAPc1rfm-G8eIcg@zte.com.cn?part=2

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters
  2026-08-30 11:12 ` [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters hu.shengming
@ 2026-08-30 15:27   ` Randy Dunlap
  2026-08-31 10:52     ` hu.shengming
  0 siblings, 1 reply; 12+ messages in thread
From: Randy Dunlap @ 2026-08-30 15:27 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

Hi,

On 8/30/26 4:12 AM, hu.shengming@zte.com.cn wrote:
> From: Shengming Hu <hu.shengming@zte.com.cn>
> 
> Document set_ftrace_comm and set_ftrace_notrace_comm, including their
> write and O_TRUNC behavior, accepted comm names, interaction with PID
> filters, and when a task is rechecked after its comm changes.
> 
> Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
> ---
>  Documentation/trace/ftrace.rst | 31 +++++++++++++++++++++++++++++++
>  1 file changed, 31 insertions(+)
> 
> diff --git a/Documentation/trace/ftrace.rst b/Documentation/trace/ftrace.rst
> index 7261f25f8b4b..a96fc436ec16 100644
> --- a/Documentation/trace/ftrace.rst
> +++ b/Documentation/trace/ftrace.rst
> @@ -313,6 +313,37 @@ of ftrace. Here is a list of some of the key files:
>          If a PID is in both this file and "set_ftrace_pid", then this
>          file takes precedence, and the thread will not be traced.
> 
> +  set_ftrace_comm:
> +
> +	Have the function and function_graph tracers trace only threads whose
> +	task comm is listed in this file. Names are matched exactly and must
> +	contain between 1 and TASK_COMM_LEN - 1 bytes.
> +
> +	Each write adds one name. A single trailing newline is ignored, while
> +	embedded newlines are rejected. A zero-length write or a write containing
> +	only a newline adds no entry. Duplicate entries have no effect.
> +
> +	Opening the file with O_TRUNC clears the filter. Otherwise, writes add

Is there a way to clear the filter from (in) a shell script?

> +	names to the existing filter. From a shell, use >> to add a name without
> +	clearing the filter. Reading an empty filter prints `no comm`.
> +
> +	If both set_ftrace_pid and set_ftrace_comm contain entries, a thread must
> +	match both filters to be traced.
> +
> +  set_ftrace_notrace_comm:
> +
> +	Have the function and function_graph tracers ignore threads whose task
> +	comm is listed in this file. Matching and file operations have the same
> +	semantics as set_ftrace_comm.
> +
> +	A match in either set_ftrace_notrace_pid or set_ftrace_notrace_comm
> +	excludes the thread and takes precedence over all include filters.
> +
> +	Function-tracer task filters are evaluated when a task is scheduled in,
> +	and the result is cached per CPU. Changing a filter refreshes the result
> +	for currently running tasks. If a running task changes its comm, it is
> +	rechecked the next time it is scheduled in.
> +
>    set_event_pid:
> 
>  	Have the events only trace a task with a PID listed in this file.

-- 
~Randy


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
                   ` (3 preceding siblings ...)
  2026-08-30 11:12 ` [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters hu.shengming
@ 2026-08-31  1:20 ` Masami Hiramatsu
  2026-08-31 10:41   ` hu.shengming
  2026-08-31 13:49 ` Steven Rostedt
  5 siblings, 1 reply; 12+ messages in thread
From: Masami Hiramatsu @ 2026-08-31  1:20 UTC (permalink / raw)
  To: hu.shengming
  Cc: rostedt, mathieu.desnoyers, mark.rutland, corbet, skhan, rdunlap,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

On Sun, 30 Aug 2026 19:07:43 +0800 (CST)
<hu.shengming@zte.com.cn> wrote:

> Hi Steven and Masami,
> 
> This series adds two comm-based task filters for the function and
> function_graph tracers:
> 
>   set_ftrace_comm
>   set_ftrace_notrace_comm
> 
> Function tracing currently supports selecting tasks by PID. This makes
> it difficult to configure tracing before a service starts, because it
> does not have a PID yet. It is also inconvenient to keep tracing the
> same service across restarts, as its PID may change. This use case was
> suggested by Xuxin, a KSM reviewer.

Thanks for the idea. I thought we can use `pidof` but it is for running
processes.

> 
> The new filters match task comm names exactly. When both PID and comm 
> include filters are active, a task must match both to be traced. A
> match in either the PID or comm notrace filter excludes the task.
> 
> To avoid string matching in the function tracing fast path, the filters
> are evaluated when a task is scheduled in. The result is stored in the
> existing per-CPU cache used by PID filtering. Changing a filter refreshes
> the cached result for currently running tasks. If a task's comm changes,
> the new name is used the next time the task is scheduled in.

OK, but can trace scheduler event (or add a new event) that we just convert
comm to PID when the comm is changed and add/remove it to pid filter?
If that works, we can also extend generic event trigger to set ftrace pid
filter. Using this allows you to add or remove processes as ftrace targets
at runtime—not only based on comm, but for other reasons as well.
(of course, setting per-cpu cache requires to kick a worker...)

This will leak the pid via set_ftrace_pid, but that is good from the
monitoring point of view.

Thank you,

> 
> The first two patches prepare the existing PID-filtering infrastructure
> by using general task-filter names and centralizing sched_switch probe
> registration and per-CPU cache updates. The third patch adds the comm
> filters, and the final patch documents their interface and interaction
> with PID filters.
> 
> This is an RFC intended to discuss whether comm-based task filtering is
> a useful direction for function tracing and whether this interface would
> be suitable for eventual upstream inclusion. Additional selftests are
> planned for the next revision. Feedback on the overall approach and
> interface would be greatly appreciated.
> 
> Thanks,
> Shengming
> 
> Shengming Hu (4):
>   ftrace: Generalize function task filter names
>   ftrace: Centralize task filter state updates
>   ftrace: Add exact task comm filtering
>   Documentation/ftrace: Document function comm filters
> 
>  Documentation/trace/ftrace.rst |  31 +++
>  include/linux/ftrace.h         |   4 +-
>  kernel/trace/Makefile          |   1 +
>  kernel/trace/comm_list.c       | 314 +++++++++++++++++++++++++
>  kernel/trace/comm_list.h       |  17 ++
>  kernel/trace/fgraph.c          |   4 +-
>  kernel/trace/ftrace.c          | 402 ++++++++++++++++++++++++++++++---
>  kernel/trace/trace.c           |   5 +
>  kernel/trace/trace.h           |  34 ++-
>  kernel/trace/trace_events.c    |  16 +-
>  kernel/trace/trace_functions.c |   2 +-
>  kernel/trace/trace_pid.c       |   8 +-
>  12 files changed, 791 insertions(+), 47 deletions(-)
>  create mode 100644 kernel/trace/comm_list.c
>  create mode 100644 kernel/trace/comm_list.h
> 
> -- 
> 2.25.1


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing
  2026-08-31  1:20 ` [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing Masami Hiramatsu
@ 2026-08-31 10:41   ` hu.shengming
  0 siblings, 0 replies; 12+ messages in thread
From: hu.shengming @ 2026-08-31 10:41 UTC (permalink / raw)
  To: mhiramat, xu.xin16
  Cc: rostedt, mathieu.desnoyers, mark.rutland, corbet, skhan, rdunlap,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	zhang.run

Masami wrote:

> On Sun, 30 Aug 2026 19:07:43 +0800 (CST)
> <hu.shengming@zte.com.cn> wrote:
> 
> > Hi Steven and Masami,
> > 
> > This series adds two comm-based task filters for the function and
> > function_graph tracers:
> > 
> >   set_ftrace_comm
> >   set_ftrace_notrace_comm
> > 
> > Function tracing currently supports selecting tasks by PID. This makes
> > it difficult to configure tracing before a service starts, because it
> > does not have a PID yet. It is also inconvenient to keep tracing the
> > same service across restarts, as its PID may change. This use case was
> > suggested by Xuxin, a KSM reviewer.
> 
> Thanks for the idea. I thought we can use `pidof` but it is for running
> processes.
> 

Yes, exactly. The main use case is to configure tracing before the
target task starts, when there is no PID for pidof to return. Thanks,
Xuxin! :)

> > 
> > The new filters match task comm names exactly. When both PID and comm 
> > include filters are active, a task must match both to be traced. A
> > match in either the PID or comm notrace filter excludes the task.
> > 
> > To avoid string matching in the function tracing fast path, the filters
> > are evaluated when a task is scheduled in. The result is stored in the
> > existing per-CPU cache used by PID filtering. Changing a filter refreshes
> > the cached result for currently running tasks. If a task's comm changes,
> > the new name is used the next time the task is scheduled in.
> 
> OK, but can trace scheduler event (or add a new event) that we just convert
> comm to PID when the comm is changed and add/remove it to pid filter?
> If that works, we can also extend generic event trigger to set ftrace pid
> filter. Using this allows you to add or remove processes as ftrace targets
> at runtime—not only based on comm, but for other reasons as well.
> (of course, setting per-cpu cache requires to kick a worker...)
> 
> This will leak the pid via set_ftrace_pid, but that is good from the
> monitoring point of view.
> 
> Thank you,
> 

Thank you for the suggestion!

My understanding is that, instead of adding comm-specific function
filters, we could add generic event-trigger actions that update the
existing function PID filter.

For example, the task_rename event already observes task comm changes.
Conceptually, a user could configure something like:

  ftrace_pid_add:pid if newcomm == "foo"
  ftrace_pid_del:pid if oldcomm == "foo" && newcomm != "foo"

The command names and syntax above are only examples. The PID field
would be selected from the event record, so the same actions could be
used with other events and filters, rather than being tied to comm or
task_rename.

I think the trigger actions could update the same PID list used by
set_ftrace_pid. A PID added by a trigger would then become a normal
set_ftrace_pid entry and would be visible when the file is read. The
list would be shared state, without tracking whether an entry came from
a user or a particular trigger. Removing a trigger would stop future
updates but would not roll back PIDs that it had already added.

Since an event trigger may run in a context where the PID list and
per-CPU cache cannot be updated directly, the trigger could queue the
operation to a worker. The worker would update the PID list and refresh
the cached task-filter result.

For the comm use case, a delete action on task_rename would remove a PID
when the task no longer has the selected comm. A delete action on
sched_process_exit could also remove stale PIDs when matching tasks
exit.

This would primarily support rules installed before a task starts or
before the relevant event occurs. It would not discover tasks that
already match when the trigger is installed unless they generate the
event again.

Does this shared PID-list and generic add/delete trigger model match
what you had in mind?

--
With Best Regards,
Shengming

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters
  2026-08-30 15:27   ` Randy Dunlap
@ 2026-08-31 10:52     ` hu.shengming
  0 siblings, 0 replies; 12+ messages in thread
From: hu.shengming @ 2026-08-31 10:52 UTC (permalink / raw)
  To: rdunlap
  Cc: rostedt, mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

Randy wrote:
> Hi,
> 

Hi Randy,

> On 8/30/26 4:12 AM, hu.shengming@zte.com.cn wrote:
> > From: Shengming Hu <hu.shengming@zte.com.cn>
> > 
> > Document set_ftrace_comm and set_ftrace_notrace_comm, including their
> > write and O_TRUNC behavior, accepted comm names, interaction with PID
> > filters, and when a task is rechecked after its comm changes.
> > 
> > Signed-off-by: Shengming Hu <hu.shengming@zte.com.cn>
> > ---
> >  Documentation/trace/ftrace.rst | 31 +++++++++++++++++++++++++++++++
> >  1 file changed, 31 insertions(+)
> > 
> > diff --git a/Documentation/trace/ftrace.rst b/Documentation/trace/ftrace.rst
> > index 7261f25f8b4b..a96fc436ec16 100644
> > --- a/Documentation/trace/ftrace.rst
> > +++ b/Documentation/trace/ftrace.rst
> > @@ -313,6 +313,37 @@ of ftrace. Here is a list of some of the key files:
> >          If a PID is in both this file and "set_ftrace_pid", then this
> >          file takes precedence, and the thread will not be traced.
> > 
> > +  set_ftrace_comm:
> > +
> > +    Have the function and function_graph tracers trace only threads whose
> > +    task comm is listed in this file. Names are matched exactly and must
> > +    contain between 1 and TASK_COMM_LEN - 1 bytes.
> > +
> > +    Each write adds one name. A single trailing newline is ignored, while
> > +    embedded newlines are rejected. A zero-length write or a write containing
> > +    only a newline adds no entry. Duplicate entries have no effect.
> > +
> > +    Opening the file with O_TRUNC clears the filter. Otherwise, writes add
> 
> Is there a way to clear the filter from (in) a shell script?
> 

Yes. It can be cleared with:

  # echo > set_ftrace_comm

The shell opens the file with O_TRUNC, which clears the existing filter.

I described the O_TRUNC behavior but overlooked the corresponding
user-facing shell command. Thanks for pointing this out.

Based on Masami's feedback, I am now considering reworking the series
around a generic event trigger that updates set_ftrace_pid, so the
comm-specific files may not remain in the next version.

--
With Best Regards,
Shengming

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing
  2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
                   ` (4 preceding siblings ...)
  2026-08-31  1:20 ` [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing Masami Hiramatsu
@ 2026-08-31 13:49 ` Steven Rostedt
  2026-09-03  1:03   ` hu.shengming
  5 siblings, 1 reply; 12+ messages in thread
From: Steven Rostedt @ 2026-08-31 13:49 UTC (permalink / raw)
  To: hu.shengming
  Cc: mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan, rdunlap,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

On Sun, 30 Aug 2026 19:07:43 +0800 (CST)
<hu.shengming@zte.com.cn> wrote:

> Hi Steven and Masami,
> 
> This series adds two comm-based task filters for the function and
> function_graph tracers:
> 
>   set_ftrace_comm
>   set_ftrace_notrace_comm

I'd like to avoid adding more files like this. If anything, I would love to
add triggers to the function tracers.

> 
> Function tracing currently supports selecting tasks by PID. This makes
> it difficult to configure tracing before a service starts, because it
> does not have a PID yet. It is also inconvenient to keep tracing the
> same service across restarts, as its PID may change. This use case was
> suggested by Xuxin, a KSM reviewer.

I've always recommended a simple wrapper script for applications:

echo '#! /bin/bash
  echo $$ > /sys/kernel/tracing/set_ftrace_pid
  echo $$ > /sys/kernel/tracing/set_event_pid
  exec "$@"' > trace-me.sh

chmod +x trace-me.sh

  ./trace-me.sh command to be traced

The above will do what you want.

But if we really do want to add more filters to function tracing, then
adding triggers to it would be the way to go.

-- Steve

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing
  2026-08-31 13:49 ` Steven Rostedt
@ 2026-09-03  1:03   ` hu.shengming
  0 siblings, 0 replies; 12+ messages in thread
From: hu.shengming @ 2026-09-03  1:03 UTC (permalink / raw)
  To: rostedt
  Cc: mhiramat, mathieu.desnoyers, mark.rutland, corbet, skhan, rdunlap,
	linux-kernel, linux-trace-kernel, linux-doc, ran.xiaokai,
	xu.xin16, zhang.run

Steven wrote:

> On Sun, 30 Aug 2026 19:07:43 +0800 (CST)
> <hu.shengming@zte.com.cn> wrote:
> 
> > Hi Steven and Masami,
> > 
> > This series adds two comm-based task filters for the function and
> > function_graph tracers:
> > 
> >   set_ftrace_comm
> >   set_ftrace_notrace_comm
> 
> I'd like to avoid adding more files like this. If anything, I would love to
> add triggers to the function tracers.

Agreed. Adding two more tracefs files may not be the best interface. Together
with the points raised in the earlier discussion with Masami, I also think a
trigger-based interface would be a cleaner direction.

> > 
> > Function tracing currently supports selecting tasks by PID. This makes
> > it difficult to configure tracing before a service starts, because it
> > does not have a PID yet. It is also inconvenient to keep tracing the
> > same service across restarts, as its PID may change. This use case was
> > suggested by Xuxin, a KSM reviewer.
> 
> I've always recommended a simple wrapper script for applications:
> 
> echo '#! /bin/bash
>   echo $$ > /sys/kernel/tracing/set_ftrace_pid
>   echo $$ > /sys/kernel/tracing/set_event_pid
>   exec "$@"' > trace-me.sh
> 
> chmod +x trace-me.sh
> 
>   ./trace-me.sh command to be traced
> 
> The above will do what you want.
> 

Yes, this works well when we have control over how the application is
launched. Thanks for the example!

> But if we really do want to add more filters to function tracing, then
> adding triggers to it would be the way to go.

Understood. To make sure I understand the proposed direction, here is a
conceptual example of what the interface might look like:

echo 'ftrace_pid_add if newcomm == "foo"' > \
 events/task/task_rename/trigger

When a task changes its comm to foo, the trigger would add the PID from
the task_rename event to the function tracer's PID filter. The function
and function_graph tracers would then trace that task using the existing
PID-filtering mechanism.

We may also need a corresponding ftrace_pid_remove trigger command to
remove the PID associated with the triggering event from the function
tracer's PID list.

The command names above are only illustrative. Is this roughly the kind of
trigger interface you had in mind?

If so, we will investigate extending the trigger infrastructure in this
direction instead of adding the comm-filter files.

--
With Best Regards,
Shengming

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-09-03  1:03 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-30 11:07 [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing hu.shengming
2026-08-30 11:09 ` [RFC PATCH 1/4] ftrace: Generalize function task filter names hu.shengming
2026-08-30 11:10 ` ​[RFC PATCH 2/4] ftrace: Centralize task filter state updates hu.shengming
2026-08-30 11:24   ` sashiko-bot
2026-08-30 11:12 ` [RFC PATCH 3/4] ftrace: Add exact task comm filtering hu.shengming
2026-08-30 11:12 ` [RFC PATCH 4/4] Documentation/ftrace: Document function comm filters hu.shengming
2026-08-30 15:27   ` Randy Dunlap
2026-08-31 10:52     ` hu.shengming
2026-08-31  1:20 ` [RFC PATCH 0/4] ftrace: Add task comm filtering for function tracing Masami Hiramatsu
2026-08-31 10:41   ` hu.shengming
2026-08-31 13:49 ` Steven Rostedt
2026-09-03  1:03   ` hu.shengming

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox