Skip to contentSkip to conversation

tracing: More clean ups of triggers

PATCH Superseded Steven Rostedt 3 participants 8 messages Nov 20, 2025 – Nov 21, 2025 Tracing

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover tracing: More clean ups of triggers

This is based on top of:

https://lore.kernel.org/linux-trace-kernel/[email protected]/

Steven Rostedt (3):
tracing: Remove unneeded event_mutex lock in event_trigger_regex_release()
tracing: Add bulk garbage collection of freeing event_trigger_data
tracing: Use strim() in trigger_process_regex() instead of skip_spaces()

----

 kernel/trace/trace.h                |  1 +
 kernel/trace/trace_events_trigger.c | 65 +++++++++++++++++++++++++++++++------
 2 files changed, 56 insertions(+), 10 deletions(-)
#1 lore raw

PATCH 1/3 tracing: Remove unneeded event_mutex lock in event_trigger_regex_release()

From: Steven Rostedt <[email protected]>

In event_trigger_regex_release(), the only code is:

	mutex_lock(&event_mutex);
	if (file->f_mode & FMODE_READ)
		seq_release(inode, file);
	mutex_unlock(&event_mutex);

	return 0;

There's nothing special about the file->f_mode or the seq_release() that requires any locking. Remove the unnecessary locks.

---
 kernel/trace/trace_events_trigger.c | 4 ----
 1 file changed, 4 deletions(-)
kernel/trace/trace_events_trigger.c +0 -4
index 7795af600466..e5dcfcbb2cd5 100644
@@ -314,13 +314,9 @@ static ssize_t event_trigger_regex_write(struct file *file,
 
 static int event_trigger_regex_release(struct inode *inode, struct file *file)
 {
-	mutex_lock(&event_mutex);
-
 	if (file->f_mode & FMODE_READ)
 		seq_release(inode, file);
 
-	mutex_unlock(&event_mutex);
-
 	return 0;
 }
 
Signature · 1 line
2.51.0
maintainer Acked-by on v1 1/3
#2 lore raw

Re: [PATCH 1/3] tracing: Remove unneeded event_mutex lock in event_trigger_regex_release()

Quoted 11 lines · On Thu, 20 Nov 2025 15:56:01 -0500 Steven Rostedt <[email protected]> wrote:

From: Steven Rostedt <[email protected]>

In event_trigger_regex_release(), the only code is:

	mutex_lock(&event_mutex);
	if (file->f_mode & FMODE_READ)
		seq_release(inode, file);
	mutex_unlock(&event_mutex);

	return 0;
Quoted 3 lines

There's nothing special about the file->f_mode or the seq_release() that requires any locking. Remove the unnecessary locks.

Looks good to me.

Quoted 27 lines
---
 kernel/trace/trace_events_trigger.c | 4 ----
 1 file changed, 4 deletions(-)
kernel/trace/trace_events_trigger.c +0 -4
index 7795af600466..e5dcfcbb2cd5 100644
@@ -314,13 +314,9 @@ static ssize_t event_trigger_regex_write(struct file *file,
 
 static int event_trigger_regex_release(struct inode *inode, struct file *file)
 {
-	mutex_lock(&event_mutex);
-
 	if (file->f_mode & FMODE_READ)
 		seq_release(inode, file);
 
-	mutex_unlock(&event_mutex);
-
 	return 0;
 }
 
Signature · 1 line
2.51.0
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
#3 lore raw

PATCH 2/3 tracing: Add bulk garbage collection of freeing event_trigger_data

In reply to Steven Rostedt

From: Steven Rostedt <[email protected]>

The event trigger data requires a full tracepoint_synchronize_unregister() call before freeing. That call can take 100s of milliseconds to complete. In order to allow for bulk freeing of the trigger data, it can not call the tracepoint_synchronize_unregister() for every individual trigger data being free.

Create a kthread that gets created the first time a trigger data is freed, and have it use the lockless llist to get the list of data to free, run the tracepoint_synchronize_unregister() then free everything in the list.

By freeing hundreds of event_trigger_data elements together, it only requires two runs of the synchronization function, and not hundreds of runs. This speeds up the operation by orders of magnitude (milliseconds instead of several seconds).

---
 kernel/trace/trace.h                |  1 +
 kernel/trace/trace_events_trigger.c | 56 +++++++++++++++++++++++++++--
 2 files changed, 54 insertions(+), 3 deletions(-)
kernel/trace/trace.h +1 -0
index 5863800b1ab3..fd5a6daa6c25 100644
@@ -1808,6 +1808,7 @@ struct event_trigger_data {
 	char				*name;
 	struct list_head		named_list;
 	struct event_trigger_data	*named_data;
+	struct llist_node		llist;
 };
 
 /* Avoid typos */
kernel/trace/trace_events_trigger.c +53 -3

79 lines not on this page · raw message

Signature · 1 line
2.51.0
maintainer Acked-by on v1 2/3
#4 lore raw

Re: [PATCH 2/3] tracing: Add bulk garbage collection of freeing event_trigger_data

Quoted 8 lines · On Thu, 20 Nov 2025 15:56:02 -0500 Steven Rostedt <[email protected]> wrote:

From: Steven Rostedt <[email protected]>

The event trigger data requires a full tracepoint_synchronize_unregister() call before freeing. That call can take 100s of milliseconds to complete. In order to allow for bulk freeing of the trigger data, it can not call the tracepoint_synchronize_unregister() for every individual trigger data being free.

Quoted 9 lines

Create a kthread that gets created the first time a trigger data is freed, and have it use the lockless llist to get the list of data to free, run the tracepoint_synchronize_unregister() then free everything in the list.

By freeing hundreds of event_trigger_data elements together, it only requires two runs of the synchronization function, and not hundreds of runs. This speeds up the operation by orders of magnitude (milliseconds instead of several seconds).

I have some nitpicks, but basically looks good to me.

Quoted 23 lines
---
 kernel/trace/trace.h                |  1 +
 kernel/trace/trace_events_trigger.c | 56 +++++++++++++++++++++++++++--
 2 files changed, 54 insertions(+), 3 deletions(-)
kernel/trace/trace.h +1 -0
index 5863800b1ab3..fd5a6daa6c25 100644
@@ -1808,6 +1808,7 @@ struct event_trigger_data {
 	char				*name;
 	struct list_head		named_list;
 	struct event_trigger_data	*named_data;
+	struct llist_node		llist;
 };
 
 /* Avoid typos */
kernel/trace/trace_events_trigger.c +0 -0
index e5dcfcbb2cd5..16e3449f3cfe 100644

kernel/trace/trace_events_trigger.c line 15

Quoted 10 lines
kernel/trace/trace_events_trigger.c +2 -0
@@ -6,26 +6,76 @@
  */
 
 #include <linux/security.h>
+#include <linux/kthread.h>
 #include <linux/module.h>
 #include <linux/ctype.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/rculist.h>
+#include <linux/llist.h>

nit: Shouldn't we include this in "trace.h" too, because llist_node is used?

Quoted 46 lines
kernel/trace/trace_events_trigger.c +33 -2
 #include "trace.h"
 
 static LIST_HEAD(trigger_commands);
 static DEFINE_MUTEX(trigger_cmd_mutex);
 
+static struct task_struct *trigger_kthread;
+static struct llist_head trigger_data_free_list;
+static DEFINE_MUTEX(trigger_data_kthread_mutex);
+
+/* Bulk garbage collection of event_trigger_data elements */
+static int trigger_kthread_fn(void *ignore)
+{
+	struct event_trigger_data *data, *tmp;
+	struct llist_node *llnodes;
+
+	/* Once this task starts, it lives forever */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (llist_empty(&trigger_data_free_list))
+			schedule();
+
+		__set_current_state(TASK_RUNNING);
+
+		llnodes = llist_del_all(&trigger_data_free_list);
+
+		/* make sure current triggers exit before free */
+		tracepoint_synchronize_unregister();
+
+		llist_for_each_entry_safe(data, tmp, llnodes, llist)
+			kfree(data);
+	}
+
+	return 0;
+}
+
 void trigger_data_free(struct event_trigger_data *data)
 {
 	if (data->cmd_ops->set_filter)
 		data->cmd_ops->set_filter(NULL, data, NULL);
 
-	/* make sure current triggers exit before free */
-	tracepoint_synchronize_unregister();
+	if (unlikely(!trigger_kthread)) {
+		guard(mutex)(&trigger_data_kthread_mutex);
+		/* Check again after taking mutex */

kernel/trace/trace_events_trigger.c line 69

Quoted 10 lines
kernel/trace/trace_events_trigger.c +10 -0
+		if (!trigger_kthread) {
+			struct task_struct *kthread;
+
+			kthread = kthread_create(trigger_kthread_fn, NULL,
+						 "trigger_data_free");
+			if (!IS_ERR(kthread))
+				WRITE_ONCE(trigger_kthread, kthread);
+		}
+	}
+

Hmm,

	/* This continues above error case, but we should do it without lock. */

?

Quoted 17 lines
kernel/trace/trace_events_trigger.c +8 -1
+	if (!trigger_kthread) {
+		/* Do it the slow way */
+		tracepoint_synchronize_unregister();
+		kfree(data);
+		return;
+	}
 
-	kfree(data);
+	llist_add(&data->llist, &trigger_data_free_list);
+	wake_up_process(trigger_kthread);
 }
 
 static inline void data_ops_trigger(struct event_trigger_data *data,
Signature · 1 line
2.51.0
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
maintainer
#5 lore raw

Re: [PATCH 2/3] tracing: Add bulk garbage collection of freeing event_trigger_data

Quoted 8 lines · On Sat, 22 Nov 2025 00:12:06 +0900 Masami Hiramatsu (Google)…
Quoted 8 lines

/* Avoid typos */

kernel/trace/trace_events_trigger.c +0 -0
index e5dcfcbb2cd5..16e3449f3cfe 100644
@@ -6,26 +6,76 @@
  */
 
Quoted 10 lines

kernel/trace/trace_events_trigger.c line 15

Quoted 8 lines
kernel/trace/trace_events_trigger.c +2 -0
 #include <linux/security.h>
+#include <linux/kthread.h>
 #include <linux/module.h>
 #include <linux/ctype.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/rculist.h>
+#include <linux/llist.h>  

nit: Shouldn't we include this in "trace.h" too, because llist_node is used?

You mean to move it to trace.h instead of here?

Quoted 59 lines
Quoted 47 lines
kernel/trace/trace_events_trigger.c +33 -2
 #include "trace.h"
 
 static LIST_HEAD(trigger_commands);
 static DEFINE_MUTEX(trigger_cmd_mutex);
 
+static struct task_struct *trigger_kthread;
+static struct llist_head trigger_data_free_list;
+static DEFINE_MUTEX(trigger_data_kthread_mutex);
+
+/* Bulk garbage collection of event_trigger_data elements */
+static int trigger_kthread_fn(void *ignore)
+{
+	struct event_trigger_data *data, *tmp;
+	struct llist_node *llnodes;
+
+	/* Once this task starts, it lives forever */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (llist_empty(&trigger_data_free_list))
+			schedule();
+
+		__set_current_state(TASK_RUNNING);
+
+		llnodes = llist_del_all(&trigger_data_free_list);
+
+		/* make sure current triggers exit before free */
+		tracepoint_synchronize_unregister();
+
+		llist_for_each_entry_safe(data, tmp, llnodes, llist)
+			kfree(data);
+	}
+
+	return 0;
+}
+
 void trigger_data_free(struct event_trigger_data *data)
 {
 	if (data->cmd_ops->set_filter)
 		data->cmd_ops->set_filter(NULL, data, NULL);
 
-	/* make sure current triggers exit before free */
-	tracepoint_synchronize_unregister();
+	if (unlikely(!trigger_kthread)) {
+		guard(mutex)(&trigger_data_kthread_mutex);
+		/* Check again after taking mutex */

kernel/trace/trace_events_trigger.c line 69

Quoted 10 lines
kernel/trace/trace_events_trigger.c +10 -0
+		if (!trigger_kthread) {
+			struct task_struct *kthread;
+
+			kthread = kthread_create(trigger_kthread_fn, NULL,
+						 "trigger_data_free");
+			if (!IS_ERR(kthread))
+				WRITE_ONCE(trigger_kthread, kthread);
+		}
+	}
+  

Hmm,

Quoted 2 lines
	/* This continues above error case, but we should do it without lock. */

?

Does this really need a comment? The lock is only needed to make sure we only create one kthread. If that fails, then we do it the slow way. The code below is unrelated to the lock. The lock is for kthread creation, not the trigger_kthread variable itself.

-- Steve

Quoted 20 lines
Quoted 18 lines
kernel/trace/trace_events_trigger.c +8 -1
+	if (!trigger_kthread) {
+		/* Do it the slow way */
+		tracepoint_synchronize_unregister();
+		kfree(data);
+		return;
+	}
 
-	kfree(data);
+	llist_add(&data->llist, &trigger_data_free_list);
+	wake_up_process(trigger_kthread);
 }
 
 static inline void data_ops_trigger(struct event_trigger_data *data,
Signature · 1 line
2.51.0
#6 lore raw

PATCH 3/3 tracing: Use strim() in trigger_process_regex() instead of skip_spaces()

In reply to Steven Rostedt

From: Steven Rostedt <[email protected]>

The function trigger_process_regex() is called by a few functions, where only one calls strim() on the buffer passed to it. That leaves the other functions not trimming the end of the buffer passed in and making it a little inconsistent.

Remove the strim() from event_trigger_regex_write() and have trigger_process_regex() use strim() instead of skip_spaces(). The buff variable is not passed in as const, so it can be modified.

---
 kernel/trace/trace_events_trigger.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
kernel/trace/trace_events_trigger.c +2 -3
index 16e3449f3cfe..1dfe69146a81 100644
@@ -309,7 +309,8 @@ int trigger_process_regex(struct trace_event_file *file, char *buff)
 	char *command, *next;
 	struct event_command *p;
 
-	next = buff = skip_spaces(buff);
+	next = buff = strim(buff);
+
 	command = strsep(&next, ": \t");
 	if (next) {
 		next = skip_spaces(next);
@@ -346,8 +347,6 @@ static ssize_t event_trigger_regex_write(struct file *file,
 	if (IS_ERR(buf))
 		return PTR_ERR(buf);
 
-	strim(buf);
-
 	guard(mutex)(&event_mutex);
 
 	event_file = event_file_file(file);
Signature · 1 line
2.51.0
maintainer Acked-by on v1 3/3
#7 lore raw

Re: [PATCH 3/3] tracing: Use strim() in trigger_process_regex() instead of skip_spaces()

Quoted 11 lines · On Thu, 20 Nov 2025 15:56:03 -0500 Steven Rostedt <[email protected]> wrote:

From: Steven Rostedt <[email protected]>

The function trigger_process_regex() is called by a few functions, where only one calls strim() on the buffer passed to it. That leaves the other functions not trimming the end of the buffer passed in and making it a little inconsistent.

Remove the strim() from event_trigger_regex_write() and have trigger_process_regex() use strim() instead of skip_spaces(). The buff variable is not passed in as const, so it can be modified.

Looks good to me.

Thanks,

Quoted 32 lines
---
 kernel/trace/trace_events_trigger.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
kernel/trace/trace_events_trigger.c +2 -3
index 16e3449f3cfe..1dfe69146a81 100644
@@ -309,7 +309,8 @@ int trigger_process_regex(struct trace_event_file *file, char *buff)
 	char *command, *next;
 	struct event_command *p;
 
-	next = buff = skip_spaces(buff);
+	next = buff = strim(buff);
+
 	command = strsep(&next, ": \t");
 	if (next) {
 		next = skip_spaces(next);
@@ -346,8 +347,6 @@ static ssize_t event_trigger_regex_write(struct file *file,
 	if (IS_ERR(buf))
 		return PTR_ERR(buf);
 
-	strim(buf);
-
 	guard(mutex)(&event_mutex);
 
 	event_file = event_file_file(file);
Signature · 1 line
2.51.0
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>