Merge: Backport Timerlat UserSpace and Cgroup Support
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/3462 JIRA: https://issues.redhat.com/browse/RHEL-14932 **Commit List:** - `e88ed227f639` - [tracing/timerlat: Add user-space interface](COMMIT_LINK_HERE) - `cb7ca871c883` - [tracing/osnoise: Skip running osnoise if all instances are off](COMMIT_LINK_HERE) - `4998e7fda149` - [tracing/osnoise: Switch from PF_NO_SETAFFINITY to migrate_disable](COMMIT_LINK_HERE) **Description:** This merge request backports the necessary kernel modifications to introduce user-space and cgroup support for the rtla user-space tool. These changes align the rtla kernel base with version v6.4, enabling more robust and versatile performance analysis and monitoring. **Key Enhancements:** - A user-space interface for the timerlat tracer is now available, allowing for the rtla tool to also measure latency introduced by userspace. - Add a cgroup interface to migrate the kthreads to the same cgroup as the userspace tool. Please review the individual commits for detailed information on the changes and their impact. Signed-off-by: Chris White <chwhite@redhat.com> Approved-by: John Kacur <jkacur@redhat.com> Approved-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com> Signed-off-by: Scott Weaver <scweaver@redhat.com>
This commit is contained in:
commit
2f15af9b6f
|
@ -180,3 +180,81 @@ dummy_load_1ms_pd_init, which had the following code (on purpose)::
|
|||
return 0;
|
||||
|
||||
}
|
||||
|
||||
User-space interface
|
||||
---------------------------
|
||||
|
||||
Timerlat allows user-space threads to use timerlat infra-structure to
|
||||
measure scheduling latency. This interface is accessible via a per-CPU
|
||||
file descriptor inside $tracing_dir/osnoise/per_cpu/cpu$ID/timerlat_fd.
|
||||
|
||||
This interface is accessible under the following conditions:
|
||||
|
||||
- timerlat tracer is enable
|
||||
- osnoise workload option is set to NO_OSNOISE_WORKLOAD
|
||||
- The user-space thread is affined to a single processor
|
||||
- The thread opens the file associated with its single processor
|
||||
- Only one thread can access the file at a time
|
||||
|
||||
The open() syscall will fail if any of these conditions are not met.
|
||||
After opening the file descriptor, the user space can read from it.
|
||||
|
||||
The read() system call will run a timerlat code that will arm the
|
||||
timer in the future and wait for it as the regular kernel thread does.
|
||||
|
||||
When the timer IRQ fires, the timerlat IRQ will execute, report the
|
||||
IRQ latency and wake up the thread waiting in the read. The thread will be
|
||||
scheduled and report the thread latency via tracer - as for the kernel
|
||||
thread.
|
||||
|
||||
The difference from the in-kernel timerlat is that, instead of re-arming
|
||||
the timer, timerlat will return to the read() system call. At this point,
|
||||
the user can run any code.
|
||||
|
||||
If the application rereads the file timerlat file descriptor, the tracer
|
||||
will report the return from user-space latency, which is the total
|
||||
latency. If this is the end of the work, it can be interpreted as the
|
||||
response time for the request.
|
||||
|
||||
After reporting the total latency, timerlat will restart the cycle, arm
|
||||
a timer, and go to sleep for the following activation.
|
||||
|
||||
If at any time one of the conditions is broken, e.g., the thread migrates
|
||||
while in user space, or the timerlat tracer is disabled, the SIG_KILL
|
||||
signal will be sent to the user-space thread.
|
||||
|
||||
Here is an basic example of user-space code for timerlat::
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char buffer[1024];
|
||||
int timerlat_fd;
|
||||
int retval;
|
||||
long cpu = 0; /* place in CPU 0 */
|
||||
cpu_set_t set;
|
||||
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(cpu, &set);
|
||||
|
||||
if (sched_setaffinity(gettid(), sizeof(set), &set) == -1)
|
||||
return 1;
|
||||
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"/sys/kernel/tracing/osnoise/per_cpu/cpu%ld/timerlat_fd",
|
||||
cpu);
|
||||
|
||||
timerlat_fd = open(buffer, O_RDONLY);
|
||||
if (timerlat_fd < 0) {
|
||||
printf("error opening %s: %s\n", buffer, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
retval = read(timerlat_fd, buffer, 1024);
|
||||
if (retval < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
close(timerlat_fd);
|
||||
exit(0);
|
||||
}
|
||||
|
|
|
@ -181,6 +181,7 @@ struct osn_irq {
|
|||
|
||||
#define IRQ_CONTEXT 0
|
||||
#define THREAD_CONTEXT 1
|
||||
#define THREAD_URET 2
|
||||
/*
|
||||
* sofirq runtime info.
|
||||
*/
|
||||
|
@ -238,6 +239,7 @@ struct timerlat_variables {
|
|||
u64 abs_period;
|
||||
bool tracing_thread;
|
||||
u64 count;
|
||||
bool uthread_migrate;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct timerlat_variables, per_cpu_timerlat_var);
|
||||
|
@ -1181,6 +1183,78 @@ thread_exit(struct osnoise_variables *osn_var, struct task_struct *t)
|
|||
osn_var->thread.arrival_time = 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TIMERLAT_TRACER
|
||||
/*
|
||||
* osnoise_stop_exception - Stop tracing and the tracer.
|
||||
*/
|
||||
static __always_inline void osnoise_stop_exception(char *msg, int cpu)
|
||||
{
|
||||
struct osnoise_instance *inst;
|
||||
struct trace_array *tr;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(inst, &osnoise_instances, list) {
|
||||
tr = inst->tr;
|
||||
trace_array_printk_buf(tr->array_buffer.buffer, _THIS_IP_,
|
||||
"stop tracing hit on cpu %d due to exception: %s\n",
|
||||
smp_processor_id(),
|
||||
msg);
|
||||
|
||||
if (test_bit(OSN_PANIC_ON_STOP, &osnoise_options))
|
||||
panic("tracer hit on cpu %d due to exception: %s\n",
|
||||
smp_processor_id(),
|
||||
msg);
|
||||
|
||||
tracer_tracing_off(tr);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
* trace_sched_migrate_callback - sched:sched_migrate_task trace event handler
|
||||
*
|
||||
* his function is hooked to the sched:sched_migrate_task trace event, and monitors
|
||||
* timerlat user-space thread migration.
|
||||
*/
|
||||
static void trace_sched_migrate_callback(void *data, struct task_struct *p, int dest_cpu)
|
||||
{
|
||||
struct osnoise_variables *osn_var;
|
||||
long cpu = task_cpu(p);
|
||||
|
||||
osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
|
||||
if (osn_var->pid == p->pid && dest_cpu != cpu) {
|
||||
per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
|
||||
osnoise_taint("timerlat user-thread migrated\n");
|
||||
osnoise_stop_exception("timerlat user-thread migrated", cpu);
|
||||
}
|
||||
}
|
||||
|
||||
static int register_migration_monitor(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Timerlat thread migration check is only required when running timerlat in user-space.
|
||||
* Thus, enable callback only if timerlat is set with no workload.
|
||||
*/
|
||||
if (timerlat_enabled() && !test_bit(OSN_WORKLOAD, &osnoise_options))
|
||||
ret = register_trace_sched_migrate_task(trace_sched_migrate_callback, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void unregister_migration_monitor(void)
|
||||
{
|
||||
if (timerlat_enabled() && !test_bit(OSN_WORKLOAD, &osnoise_options))
|
||||
unregister_trace_sched_migrate_task(trace_sched_migrate_callback, NULL);
|
||||
}
|
||||
#else
|
||||
static int register_migration_monitor(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static void unregister_migration_monitor(void) {}
|
||||
#endif
|
||||
/*
|
||||
* trace_sched_switch - sched:sched_switch trace event handler
|
||||
*
|
||||
|
@ -1204,7 +1278,7 @@ trace_sched_switch_callback(void *data, bool preempt,
|
|||
}
|
||||
|
||||
/*
|
||||
* hook_thread_events - Hook the insturmentation for thread noise
|
||||
* hook_thread_events - Hook the instrumentation for thread noise
|
||||
*
|
||||
* Hook the osnoise tracer callbacks to handle the noise from other
|
||||
* threads on the necessary kernel events.
|
||||
|
@ -1217,11 +1291,19 @@ static int hook_thread_events(void)
|
|||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = register_migration_monitor();
|
||||
if (ret)
|
||||
goto out_unreg;
|
||||
|
||||
return 0;
|
||||
|
||||
out_unreg:
|
||||
unregister_trace_sched_switch(trace_sched_switch_callback, NULL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* unhook_thread_events - *nhook the insturmentation for thread noise
|
||||
* unhook_thread_events - unhook the instrumentation for thread noise
|
||||
*
|
||||
* Unook the osnoise tracer callbacks to handle the noise from other
|
||||
* threads on the necessary kernel events.
|
||||
|
@ -1229,6 +1311,7 @@ static int hook_thread_events(void)
|
|||
static void unhook_thread_events(void)
|
||||
{
|
||||
unregister_trace_sched_switch(trace_sched_switch_callback, NULL);
|
||||
unregister_migration_monitor();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1285,6 +1368,22 @@ static __always_inline void osnoise_stop_tracing(void)
|
|||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
* osnoise_has_tracing_on - Check if there is at least one instance on
|
||||
*/
|
||||
static __always_inline int osnoise_has_tracing_on(void)
|
||||
{
|
||||
struct osnoise_instance *inst;
|
||||
int trace_is_on = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(inst, &osnoise_instances, list)
|
||||
trace_is_on += tracer_tracing_is_on(inst->tr);
|
||||
rcu_read_unlock();
|
||||
|
||||
return trace_is_on;
|
||||
}
|
||||
|
||||
/*
|
||||
* notify_new_max_latency - Notify a new max latency via fsnotify interface.
|
||||
*/
|
||||
|
@ -1517,13 +1616,16 @@ static struct cpumask save_cpumask;
|
|||
/*
|
||||
* osnoise_sleep - sleep until the next period
|
||||
*/
|
||||
static void osnoise_sleep(void)
|
||||
static void osnoise_sleep(bool skip_period)
|
||||
{
|
||||
u64 interval;
|
||||
ktime_t wake_time;
|
||||
|
||||
mutex_lock(&interface_lock);
|
||||
interval = osnoise_data.sample_period - osnoise_data.sample_runtime;
|
||||
if (skip_period)
|
||||
interval = osnoise_data.sample_period;
|
||||
else
|
||||
interval = osnoise_data.sample_period - osnoise_data.sample_runtime;
|
||||
mutex_unlock(&interface_lock);
|
||||
|
||||
/*
|
||||
|
@ -1545,6 +1647,39 @@ static void osnoise_sleep(void)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* osnoise_migration_pending - checks if the task needs to migrate
|
||||
*
|
||||
* osnoise/timerlat threads are per-cpu. If there is a pending request to
|
||||
* migrate the thread away from the current CPU, something bad has happened.
|
||||
* Play the good citizen and leave.
|
||||
*
|
||||
* Returns 0 if it is safe to continue, 1 otherwise.
|
||||
*/
|
||||
static inline int osnoise_migration_pending(void)
|
||||
{
|
||||
if (!current->migration_pending)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If migration is pending, there is a task waiting for the
|
||||
* tracer to enable migration. The tracer does not allow migration,
|
||||
* thus: taint and leave to unblock the blocked thread.
|
||||
*/
|
||||
osnoise_taint("migration requested to osnoise threads, leaving.");
|
||||
|
||||
/*
|
||||
* Unset this thread from the threads managed by the interface.
|
||||
* The tracers are responsible for cleaning their env before
|
||||
* exiting.
|
||||
*/
|
||||
mutex_lock(&interface_lock);
|
||||
this_cpu_osn_var()->kthread = NULL;
|
||||
mutex_unlock(&interface_lock);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* osnoise_main - The osnoise detection kernel thread
|
||||
*
|
||||
|
@ -1553,12 +1688,35 @@ static void osnoise_sleep(void)
|
|||
*/
|
||||
static int osnoise_main(void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* This thread was created pinned to the CPU using PF_NO_SETAFFINITY.
|
||||
* The problem is that cgroup does not allow PF_NO_SETAFFINITY thread.
|
||||
*
|
||||
* To work around this limitation, disable migration and remove the
|
||||
* flag.
|
||||
*/
|
||||
migrate_disable();
|
||||
raw_spin_lock_irqsave(¤t->pi_lock, flags);
|
||||
current->flags &= ~(PF_NO_SETAFFINITY);
|
||||
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
if (osnoise_migration_pending())
|
||||
break;
|
||||
|
||||
/* skip a period if tracing is off on all instances */
|
||||
if (!osnoise_has_tracing_on()) {
|
||||
osnoise_sleep(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
run_osnoise();
|
||||
osnoise_sleep();
|
||||
osnoise_sleep(false);
|
||||
}
|
||||
|
||||
migrate_enable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1706,6 +1864,7 @@ static int timerlat_main(void *data)
|
|||
struct timerlat_variables *tlat = this_cpu_tmr_var();
|
||||
struct timerlat_sample s;
|
||||
struct sched_param sp;
|
||||
unsigned long flags;
|
||||
u64 now, diff;
|
||||
|
||||
/*
|
||||
|
@ -1714,6 +1873,18 @@ static int timerlat_main(void *data)
|
|||
sp.sched_priority = DEFAULT_TIMERLAT_PRIO;
|
||||
sched_setscheduler_nocheck(current, SCHED_FIFO, &sp);
|
||||
|
||||
/*
|
||||
* This thread was created pinned to the CPU using PF_NO_SETAFFINITY.
|
||||
* The problem is that cgroup does not allow PF_NO_SETAFFINITY thread.
|
||||
*
|
||||
* To work around this limitation, disable migration and remove the
|
||||
* flag.
|
||||
*/
|
||||
migrate_disable();
|
||||
raw_spin_lock_irqsave(¤t->pi_lock, flags);
|
||||
current->flags &= ~(PF_NO_SETAFFINITY);
|
||||
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
|
||||
|
||||
tlat->count = 0;
|
||||
tlat->tracing_thread = false;
|
||||
|
||||
|
@ -1731,6 +1902,7 @@ static int timerlat_main(void *data)
|
|||
osn_var->sampling = 1;
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
|
||||
now = ktime_to_ns(hrtimer_cb_get_time(&tlat->timer));
|
||||
diff = now - tlat->abs_period;
|
||||
|
||||
|
@ -1749,10 +1921,14 @@ static int timerlat_main(void *data)
|
|||
if (time_to_us(diff) >= osnoise_data.stop_tracing_total)
|
||||
osnoise_stop_tracing();
|
||||
|
||||
if (osnoise_migration_pending())
|
||||
break;
|
||||
|
||||
wait_next_period(tlat);
|
||||
}
|
||||
|
||||
hrtimer_cancel(&tlat->timer);
|
||||
migrate_enable();
|
||||
return 0;
|
||||
}
|
||||
#else /* CONFIG_TIMERLAT_TRACER */
|
||||
|
@ -1771,10 +1947,24 @@ static void stop_kthread(unsigned int cpu)
|
|||
|
||||
kthread = per_cpu(per_cpu_osnoise_var, cpu).kthread;
|
||||
if (kthread) {
|
||||
kthread_stop(kthread);
|
||||
if (test_bit(OSN_WORKLOAD, &osnoise_options)) {
|
||||
kthread_stop(kthread);
|
||||
} else {
|
||||
/*
|
||||
* This is a user thread waiting on the timerlat_fd. We need
|
||||
* to close all users, and the best way to guarantee this is
|
||||
* by killing the thread. NOTE: this is a purpose specific file.
|
||||
*/
|
||||
kill_pid(kthread->thread_pid, SIGKILL, 1);
|
||||
put_task_struct(kthread);
|
||||
}
|
||||
per_cpu(per_cpu_osnoise_var, cpu).kthread = NULL;
|
||||
} else {
|
||||
/* if no workload, just return */
|
||||
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
|
||||
/*
|
||||
* This is set in the osnoise tracer case.
|
||||
*/
|
||||
per_cpu(per_cpu_osnoise_var, cpu).sampling = false;
|
||||
barrier();
|
||||
return;
|
||||
|
@ -1819,7 +2009,6 @@ static int start_kthread(unsigned int cpu)
|
|||
barrier();
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(comm, 24, "osnoise/%d", cpu);
|
||||
}
|
||||
|
||||
|
@ -1848,6 +2037,11 @@ static int start_per_cpu_kthreads(void)
|
|||
int retval = 0;
|
||||
int cpu;
|
||||
|
||||
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
|
||||
if (timerlat_enabled())
|
||||
return 0;
|
||||
}
|
||||
|
||||
cpus_read_lock();
|
||||
/*
|
||||
* Run only on online CPUs in which osnoise is allowed to run.
|
||||
|
@ -2188,6 +2382,223 @@ err_free:
|
|||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TIMERLAT_TRACER
|
||||
static int timerlat_fd_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct osnoise_variables *osn_var;
|
||||
struct timerlat_variables *tlat;
|
||||
long cpu = (long) inode->i_cdev;
|
||||
|
||||
mutex_lock(&interface_lock);
|
||||
|
||||
/*
|
||||
* This file is accessible only if timerlat is enabled, and
|
||||
* NO_OSNOISE_WORKLOAD is set.
|
||||
*/
|
||||
if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options)) {
|
||||
mutex_unlock(&interface_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
migrate_disable();
|
||||
|
||||
osn_var = this_cpu_osn_var();
|
||||
|
||||
/*
|
||||
* The osn_var->pid holds the single access to this file.
|
||||
*/
|
||||
if (osn_var->pid) {
|
||||
mutex_unlock(&interface_lock);
|
||||
migrate_enable();
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/*
|
||||
* timerlat tracer is a per-cpu tracer. Check if the user-space too
|
||||
* is pinned to a single CPU. The tracer laters monitor if the task
|
||||
* migrates and then disables tracer if it does. However, it is
|
||||
* worth doing this basic acceptance test to avoid obviusly wrong
|
||||
* setup.
|
||||
*/
|
||||
if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) {
|
||||
mutex_unlock(&interface_lock);
|
||||
migrate_enable();
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/*
|
||||
* From now on, it is good to go.
|
||||
*/
|
||||
file->private_data = inode->i_cdev;
|
||||
|
||||
get_task_struct(current);
|
||||
|
||||
osn_var->kthread = current;
|
||||
osn_var->pid = current->pid;
|
||||
|
||||
/*
|
||||
* Setup is done.
|
||||
*/
|
||||
mutex_unlock(&interface_lock);
|
||||
|
||||
tlat = this_cpu_tmr_var();
|
||||
tlat->count = 0;
|
||||
|
||||
migrate_enable();
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* timerlat_fd_read - Read function for "timerlat_fd" file
|
||||
* @file: The active open file structure
|
||||
* @ubuf: The userspace provided buffer to read value into
|
||||
* @cnt: The maximum number of bytes to read
|
||||
* @ppos: The current "file" position
|
||||
*
|
||||
* Prints 1 on timerlat, the number of interferences on osnoise, -1 on error.
|
||||
*/
|
||||
static ssize_t
|
||||
timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
long cpu = (long) file->private_data;
|
||||
struct osnoise_variables *osn_var;
|
||||
struct timerlat_variables *tlat;
|
||||
struct timerlat_sample s;
|
||||
s64 diff;
|
||||
u64 now;
|
||||
|
||||
migrate_disable();
|
||||
|
||||
tlat = this_cpu_tmr_var();
|
||||
|
||||
/*
|
||||
* While in user-space, the thread is migratable. There is nothing
|
||||
* we can do about it.
|
||||
* So, if the thread is running on another CPU, stop the machinery.
|
||||
*/
|
||||
if (cpu == smp_processor_id()) {
|
||||
if (tlat->uthread_migrate) {
|
||||
migrate_enable();
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
|
||||
osnoise_taint("timerlat user thread migrate\n");
|
||||
osnoise_stop_tracing();
|
||||
migrate_enable();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
osn_var = this_cpu_osn_var();
|
||||
|
||||
/*
|
||||
* The timerlat in user-space runs in a different order:
|
||||
* the read() starts from the execution of the previous occurrence,
|
||||
* sleeping for the next occurrence.
|
||||
*
|
||||
* So, skip if we are entering on read() before the first wakeup
|
||||
* from timerlat IRQ:
|
||||
*/
|
||||
if (likely(osn_var->sampling)) {
|
||||
now = ktime_to_ns(hrtimer_cb_get_time(&tlat->timer));
|
||||
diff = now - tlat->abs_period;
|
||||
|
||||
/*
|
||||
* it was not a timer firing, but some other signal?
|
||||
*/
|
||||
if (diff < 0)
|
||||
goto out;
|
||||
|
||||
s.seqnum = tlat->count;
|
||||
s.timer_latency = diff;
|
||||
s.context = THREAD_URET;
|
||||
|
||||
trace_timerlat_sample(&s);
|
||||
|
||||
notify_new_max_latency(diff);
|
||||
|
||||
tlat->tracing_thread = false;
|
||||
if (osnoise_data.stop_tracing_total)
|
||||
if (time_to_us(diff) >= osnoise_data.stop_tracing_total)
|
||||
osnoise_stop_tracing();
|
||||
} else {
|
||||
tlat->tracing_thread = false;
|
||||
tlat->kthread = current;
|
||||
|
||||
hrtimer_init(&tlat->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
|
||||
tlat->timer.function = timerlat_irq;
|
||||
|
||||
/* Annotate now to drift new period */
|
||||
tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
|
||||
|
||||
osn_var->sampling = 1;
|
||||
}
|
||||
|
||||
/* wait for the next period */
|
||||
wait_next_period(tlat);
|
||||
|
||||
/* This is the wakeup from this cycle */
|
||||
now = ktime_to_ns(hrtimer_cb_get_time(&tlat->timer));
|
||||
diff = now - tlat->abs_period;
|
||||
|
||||
/*
|
||||
* it was not a timer firing, but some other signal?
|
||||
*/
|
||||
if (diff < 0)
|
||||
goto out;
|
||||
|
||||
s.seqnum = tlat->count;
|
||||
s.timer_latency = diff;
|
||||
s.context = THREAD_CONTEXT;
|
||||
|
||||
trace_timerlat_sample(&s);
|
||||
|
||||
if (osnoise_data.stop_tracing_total) {
|
||||
if (time_to_us(diff) >= osnoise_data.stop_tracing_total) {
|
||||
timerlat_dump_stack(time_to_us(diff));
|
||||
notify_new_max_latency(diff);
|
||||
osnoise_stop_tracing();
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
migrate_enable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int timerlat_fd_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct osnoise_variables *osn_var;
|
||||
struct timerlat_variables *tlat_var;
|
||||
long cpu = (long) file->private_data;
|
||||
|
||||
migrate_disable();
|
||||
mutex_lock(&interface_lock);
|
||||
|
||||
osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
|
||||
tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);
|
||||
|
||||
hrtimer_cancel(&tlat_var->timer);
|
||||
memset(tlat_var, 0, sizeof(*tlat_var));
|
||||
|
||||
osn_var->sampling = 0;
|
||||
osn_var->pid = 0;
|
||||
|
||||
/*
|
||||
* We are leaving, not being stopped... see stop_kthread();
|
||||
*/
|
||||
if (osn_var->kthread) {
|
||||
put_task_struct(osn_var->kthread);
|
||||
osn_var->kthread = NULL;
|
||||
}
|
||||
|
||||
mutex_unlock(&interface_lock);
|
||||
migrate_enable();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* osnoise/runtime_us: cannot be greater than the period.
|
||||
*/
|
||||
|
@ -2251,6 +2662,13 @@ static struct trace_min_max_param timerlat_period = {
|
|||
.max = &timerlat_max_period,
|
||||
.min = &timerlat_min_period,
|
||||
};
|
||||
|
||||
static const struct file_operations timerlat_fd_fops = {
|
||||
.open = timerlat_fd_open,
|
||||
.read = timerlat_fd_read,
|
||||
.release = timerlat_fd_release,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct file_operations cpus_fops = {
|
||||
|
@ -2288,18 +2706,63 @@ static int init_timerlat_stack_tracefs(struct dentry *top_dir)
|
|||
}
|
||||
#endif /* CONFIG_STACKTRACE */
|
||||
|
||||
static int osnoise_create_cpu_timerlat_fd(struct dentry *top_dir)
|
||||
{
|
||||
struct dentry *timerlat_fd;
|
||||
struct dentry *per_cpu;
|
||||
struct dentry *cpu_dir;
|
||||
char cpu_str[30]; /* see trace.c: tracing_init_tracefs_percpu() */
|
||||
long cpu;
|
||||
|
||||
/*
|
||||
* Why not using tracing instance per_cpu/ dir?
|
||||
*
|
||||
* Because osnoise/timerlat have a single workload, having
|
||||
* multiple files like these are wast of memory.
|
||||
*/
|
||||
per_cpu = tracefs_create_dir("per_cpu", top_dir);
|
||||
if (!per_cpu)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
snprintf(cpu_str, 30, "cpu%ld", cpu);
|
||||
cpu_dir = tracefs_create_dir(cpu_str, per_cpu);
|
||||
if (!cpu_dir)
|
||||
goto out_clean;
|
||||
|
||||
timerlat_fd = trace_create_file("timerlat_fd", TRACE_MODE_READ,
|
||||
cpu_dir, NULL, &timerlat_fd_fops);
|
||||
if (!timerlat_fd)
|
||||
goto out_clean;
|
||||
|
||||
/* Record the CPU */
|
||||
d_inode(timerlat_fd)->i_cdev = (void *)(cpu);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_clean:
|
||||
tracefs_remove(per_cpu);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* init_timerlat_tracefs - A function to initialize the timerlat interface files
|
||||
*/
|
||||
static int init_timerlat_tracefs(struct dentry *top_dir)
|
||||
{
|
||||
struct dentry *tmp;
|
||||
int retval;
|
||||
|
||||
tmp = tracefs_create_file("timerlat_period_us", TRACE_MODE_WRITE, top_dir,
|
||||
&timerlat_period, &trace_min_max_fops);
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
retval = osnoise_create_cpu_timerlat_fd(top_dir);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return init_timerlat_stack_tracefs(top_dir);
|
||||
}
|
||||
#else /* CONFIG_TIMERLAT_TRACER */
|
||||
|
|
|
@ -1320,6 +1320,8 @@ static struct trace_event trace_osnoise_event = {
|
|||
};
|
||||
|
||||
/* TRACE_TIMERLAT */
|
||||
|
||||
static char *timerlat_lat_context[] = {"irq", "thread", "user-ret"};
|
||||
static enum print_line_t
|
||||
trace_timerlat_print(struct trace_iterator *iter, int flags,
|
||||
struct trace_event *event)
|
||||
|
@ -1332,7 +1334,7 @@ trace_timerlat_print(struct trace_iterator *iter, int flags,
|
|||
|
||||
trace_seq_printf(s, "#%-5u context %6s timer_latency %9llu ns\n",
|
||||
field->seqnum,
|
||||
field->context ? "thread" : "irq",
|
||||
timerlat_lat_context[field->context],
|
||||
field->timer_latency);
|
||||
|
||||
return trace_handle_return(s);
|
||||
|
|
Loading…
Reference in New Issue