diff options
Diffstat (limited to 'drivers/gator/driver/gator_trace_sched.c')
-rw-r--r-- | drivers/gator/driver/gator_trace_sched.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drivers/gator/driver/gator_trace_sched.c b/drivers/gator/driver/gator_trace_sched.c new file mode 100644 index 00000000000..dafacb75416 --- /dev/null +++ b/drivers/gator/driver/gator_trace_sched.c @@ -0,0 +1,269 @@ +/** + * Copyright (C) ARM Limited 2010-2012. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <trace/events/sched.h> +#include "gator.h" + +#define SCHED_OVERFLOW -1 +#define SCHED_SWITCH 1 +#define SCHED_PROCESS_FREE 2 + +#define SCHEDSIZE (8*1024) +#define TASK_MAP_ENTRIES 1024 /* must be power of 2 */ +#define TASK_MAX_COLLISIONS 2 + +static DEFINE_PER_CPU(uint64_t *[2], theSchedBuf); +static DEFINE_PER_CPU(int, theSchedSel); +static DEFINE_PER_CPU(int, theSchedPos); +static DEFINE_PER_CPU(int, theSchedErr); +static DEFINE_PER_CPU(uint64_t *, taskname_keys); + +enum { + STATE_CONTENTION = 0, + STATE_WAIT_ON_IO, + STATE_WAIT_ON_OTHER +}; + +int gator_trace_sched_read(long long **buffer); + +void emit_pid_name(struct task_struct* task) +{ + bool found = false; + unsigned long flags; + char taskcomm[TASK_COMM_LEN + 3]; + int x, cpu = smp_processor_id(); + uint64_t *keys = &(per_cpu(taskname_keys, cpu)[(task->pid & 0xFF) * TASK_MAX_COLLISIONS]); + uint64_t value; + + value = gator_chksum_crc32(task->comm); + value = (value << 32) | (uint32_t)task->pid; + + // determine if the thread name was emitted already + for (x = 0; x < TASK_MAX_COLLISIONS; x++) { + if (keys[x] == value) { + found = true; + break; + } + } + + if (!found && buffer_check_space(cpu, TIMER_BUF, TASK_COMM_LEN + 2 * MAXSIZE_PACK32 + MAXSIZE_PACK64)) { + // shift values, new value always in front + uint64_t oldv, newv = value; + for (x = 0; x < TASK_MAX_COLLISIONS; x++) { + oldv = keys[x]; + keys[x] = newv; + newv = oldv; + } + + // emit pid names, cannot use get_task_comm, as it's not exported on all kernel versions + if (strlcpy(taskcomm, task->comm, TASK_COMM_LEN) == TASK_COMM_LEN - 1) + // append ellipses if task->comm has length of TASK_COMM_LEN - 1 + strcat(taskcomm, "..."); + + // disable interrupts to synchronize with hrtimer populating timer buf + local_irq_save(flags); + gator_buffer_write_packed_int(cpu, TIMER_BUF, MESSAGE_PID_NAME); + gator_buffer_write_packed_int64(cpu, TIMER_BUF, gator_get_time()); + gator_buffer_write_packed_int(cpu, TIMER_BUF, task->pid); + gator_buffer_write_string(cpu, TIMER_BUF, taskcomm); + local_irq_restore(flags); + } +} + +static void probe_sched_write(int type, struct task_struct* task, struct task_struct* old_task) +{ + int schedPos, cookie = 0, state = 0; + unsigned long flags; + uint64_t *schedBuf, time; + int cpu = smp_processor_id(); + int pid = task->pid; + int tgid = task->tgid; + + if (!per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]) + return; + + if (type == SCHED_SWITCH) { + // do as much work as possible before disabling interrupts + cookie = get_exec_cookie(cpu, TIMER_BUF, task); + emit_pid_name(task); + if (old_task->state == 0) + state = STATE_CONTENTION; + else if (old_task->in_iowait) + state = STATE_WAIT_ON_IO; + else + state = STATE_WAIT_ON_OTHER; + } + + // disable interrupts to synchronize with gator_trace_sched_read(); spinlocks not needed since percpu buffers are used + local_irq_save(flags); + + time = gator_get_time(); + schedPos = per_cpu(theSchedPos, cpu); + schedBuf = per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]; + + if (schedPos < (SCHEDSIZE - 100)) { + // capture + schedBuf[schedPos++] = type; + schedBuf[schedPos++] = time; + schedBuf[schedPos++] = pid; + schedBuf[schedPos++] = tgid; + schedBuf[schedPos++] = cookie; + schedBuf[schedPos++] = state; + } else if (!per_cpu(theSchedErr, cpu)) { + per_cpu(theSchedErr, cpu) = 1; + schedBuf[schedPos++] = SCHED_OVERFLOW; + schedBuf[schedPos++] = time; + schedBuf[schedPos++] = 0; + schedBuf[schedPos++] = 0; + schedBuf[schedPos++] = 0; + schedBuf[schedPos++] = 0; + pr_debug("gator: tracepoint overflow\n"); + } + per_cpu(theSchedPos, cpu) = schedPos; + local_irq_restore(flags); +} + +// special case used during a suspend of the system +static void trace_sched_insert_idle(void) +{ + unsigned long flags; + uint64_t *schedBuf; + int schedPos, cpu = smp_processor_id(); + + if (!per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]) + return; + + local_irq_save(flags); + + schedPos = per_cpu(theSchedPos, cpu); + schedBuf = per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]; + + if (schedPos < (SCHEDSIZE - (6 * 8))) { + // capture + schedBuf[schedPos++] = SCHED_SWITCH; + schedBuf[schedPos++] = gator_get_time(); + schedBuf[schedPos++] = 0; // idle pid is zero + schedBuf[schedPos++] = 0; // idle tid is zero + schedBuf[schedPos++] = 0; // idle cookie is zero + schedBuf[schedPos++] = STATE_WAIT_ON_OTHER; + } + + per_cpu(theSchedPos, cpu) = schedPos; + local_irq_restore(flags); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct rq *rq, struct task_struct *prev, struct task_struct *next)) +#else +GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct task_struct *prev, struct task_struct *next)) +#endif +{ + probe_sched_write(SCHED_SWITCH, next, prev); +} + +GATOR_DEFINE_PROBE(sched_process_free, TP_PROTO(struct task_struct *p)) +{ + probe_sched_write(SCHED_PROCESS_FREE, p, 0); +} + +static int register_scheduler_tracepoints(void) { + // register tracepoints + if (GATOR_REGISTER_TRACE(sched_switch)) + goto fail_sched_switch; + if (GATOR_REGISTER_TRACE(sched_process_free)) + goto fail_sched_process_free; + pr_debug("gator: registered tracepoints\n"); + + return 0; + + // unregister tracepoints on error +fail_sched_process_free: + GATOR_UNREGISTER_TRACE(sched_switch); +fail_sched_switch: + pr_err("gator: tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n"); + + return -1; +} + +int gator_trace_sched_start(void) +{ + int cpu, size; + + for_each_present_cpu(cpu) { + per_cpu(theSchedSel, cpu) = 0; + per_cpu(theSchedPos, cpu) = 0; + per_cpu(theSchedErr, cpu) = 0; + per_cpu(theSchedBuf, cpu)[0] = kmalloc(SCHEDSIZE * sizeof(uint64_t), GFP_KERNEL); + per_cpu(theSchedBuf, cpu)[1] = kmalloc(SCHEDSIZE * sizeof(uint64_t), GFP_KERNEL); + if (!per_cpu(theSchedBuf, cpu)) + return -1; + + size = TASK_MAP_ENTRIES * TASK_MAX_COLLISIONS * sizeof(uint64_t); + per_cpu(taskname_keys, cpu) = (uint64_t*)kmalloc(size, GFP_KERNEL); + if (!per_cpu(taskname_keys, cpu)) + return -1; + memset(per_cpu(taskname_keys, cpu), 0, size); + } + + return register_scheduler_tracepoints(); +} + +int gator_trace_sched_offline(long long **buffer) +{ + trace_sched_insert_idle(); + return gator_trace_sched_read(buffer); +} + +static void unregister_scheduler_tracepoints(void) +{ + GATOR_UNREGISTER_TRACE(sched_switch); + GATOR_UNREGISTER_TRACE(sched_process_free); + pr_debug("gator: unregistered tracepoints\n"); +} + +void gator_trace_sched_stop(void) +{ + int cpu; + unregister_scheduler_tracepoints(); + + for_each_present_cpu(cpu) { + kfree(per_cpu(theSchedBuf, cpu)[0]); + kfree(per_cpu(theSchedBuf, cpu)[1]); + per_cpu(theSchedBuf, cpu)[0] = NULL; + per_cpu(theSchedBuf, cpu)[1] = NULL; + kfree(per_cpu(taskname_keys, cpu)); + } +} + +int gator_trace_sched_read(long long **buffer) +{ + int cpu = smp_processor_id(); + unsigned long flags; + uint64_t *schedBuf; + int schedPos; + + if (!per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]) + return 0; + + local_irq_save(flags); + + schedBuf = per_cpu(theSchedBuf, cpu)[per_cpu(theSchedSel, cpu)]; + schedPos = per_cpu(theSchedPos, cpu); + + per_cpu(theSchedSel, cpu) = !per_cpu(theSchedSel, cpu); + per_cpu(theSchedPos, cpu) = 0; + per_cpu(theSchedErr, cpu) = 0; + + local_irq_restore(flags); + + if (buffer) + *buffer = schedBuf; + + return schedPos; +} |