diff --git a/kernel-src/include/kernel/scheduler.h b/kernel-src/include/kernel/scheduler.h index 03434d2c..821e9707 100644 --- a/kernel-src/include/kernel/scheduler.h +++ b/kernel-src/include/kernel/scheduler.h @@ -23,4 +23,8 @@ void sched_reschedule_on_cpu(struct cpu_t *cpu, bool target); void sched_sleep_us(size_t us); int sched_yield(); +void sched_thread_running_callback(thread_t *thread); +void sched_thread_stopping_callback(thread_t *thread, bool sleeping); +void sched_thread_wakeup_callback(thread_t *thread); + #endif diff --git a/kernel-src/include/kernel/thread.h b/kernel-src/include/kernel/thread.h index 8809fdf5..06cae8e1 100644 --- a/kernel-src/include/kernel/thread.h +++ b/kernel-src/include/kernel/thread.h @@ -52,6 +52,13 @@ typedef struct thread_t { bool hasreturnmask; bool stopped; } signals; + struct { + int interactivity_score; + time_t sleep_time_avg_us; + time_t run_time_avg_us; + timespec_t sleep_start; + timespec_t run_start; + } metrics; } thread_t; __attribute__((noreturn)) void sched_threadexit(); diff --git a/kernel-src/include/util.h b/kernel-src/include/util.h index 2802e2e9..b471979e 100644 --- a/kernel-src/include/util.h +++ b/kernel-src/include/util.h @@ -24,6 +24,10 @@ static inline long min(long x, long y) { return x > y ? y : x; } +static inline long max(long x, long y) { + return x < y ? y : x; +} + #define FNV1PRIME 0x100000001b3ull #define FNV1OFFSET 0xcbf29ce484222325ull diff --git a/kernel-src/sys/scheduler/metrics.c b/kernel-src/sys/scheduler/metrics.c new file mode 100644 index 00000000..93efaa82 --- /dev/null +++ b/kernel-src/sys/scheduler/metrics.c @@ -0,0 +1,55 @@ +#include +#include + +#define MAX_INTERACTIVITY 100 +#define SCALING_FACTOR (MAX_INTERACTIVITY / 2) + +#define HISTORY_LIMIT_USEC 5000000 + +static void update_interactivity(thread_t *thread) { + time_t sleep_time = thread->metrics.sleep_time_avg_us; + time_t run_time = thread->metrics.run_time_avg_us; + + if (sleep_time > run_time) + thread->metrics.interactivity_score = min((SCALING_FACTOR * run_time) / sleep_time, MAX_INTERACTIVITY); + else + thread->metrics.interactivity_score = min((SCALING_FACTOR * sleep_time) / run_time + SCALING_FACTOR, MAX_INTERACTIVITY); + + // TODO nice value impacting this +} + +static void offset_times(thread_t *thread, time_t run_us, time_t sleep_us) { + thread->metrics.run_time_avg_us += run_us; + thread->metrics.sleep_time_avg_us += sleep_us; + + time_t total = thread->metrics.run_time_avg_us + thread->metrics.sleep_time_avg_us; + if (total > HISTORY_LIMIT_USEC) { + if (total > HISTORY_LIMIT_USEC * 2) { + // the thread has spent a lot of time sleeping, as this will not be reached normally otherwise + // set its sleep time to max and zero the run time before applying normal decay + thread->metrics.run_time_avg_us = 0; + thread->metrics.sleep_time_avg_us = HISTORY_LIMIT_USEC; + } + thread->metrics.run_time_avg_us = thread->metrics.run_time_avg_us * 4 / 5; + thread->metrics.sleep_time_avg_us = thread->metrics.sleep_time_avg_us * 4 / 5; + } + + update_interactivity(thread); +} + +void sched_thread_running_callback(thread_t *thread) { + thread->metrics.run_start = timekeeper_time(); +} + +void sched_thread_stopping_callback(thread_t *thread, bool sleeping) { + timespec_t now = timekeeper_time(); + offset_times(thread, timespec_diffus(thread->metrics.run_start, now), 0); + + if (sleeping) + thread->metrics.sleep_start = now; +} + +// called right before the specified thread gets enqueued after sleeping +void sched_thread_wakeup_callback(thread_t *thread) { + offset_times(thread, 0, timespec_diffus(thread->metrics.sleep_start, timekeeper_time())); +} diff --git a/kernel-src/sys/scheduler/scheduler.c b/kernel-src/sys/scheduler/scheduler.c index 96391bca..f75b3548 100644 --- a/kernel-src/sys/scheduler/scheduler.c +++ b/kernel-src/sys/scheduler/scheduler.c @@ -80,6 +80,8 @@ static thread_t *runqueuenext(int minprio) { static __attribute__((noreturn)) void switch_thread(thread_t *thread) { interrupt_set(false); thread_t* current = current_thread(); + + sched_thread_running_callback(thread); current_cpu()->thread = thread; @@ -139,6 +141,7 @@ void sched_queue(thread_t *thread) { // TODO yield if higher priority than current thread (or send another CPU an IPI) } +// no metrics callback here as this will be called from things like threads stopping __attribute__((noreturn)) void sched_stop_current_thread() { interrupt_set(false); @@ -256,6 +259,7 @@ static void yield(context_t *context, void *) { ARCH_CONTEXT_THREADSAVE(thread, context); thread->flags &= ~THREAD_FLAGS_RUNNING; + if (sleeping == false) runqueueinsert(thread); @@ -267,12 +271,16 @@ static void yield(context_t *context, void *) { } spinlock_release(&runqueuelock); + sched_thread_running_callback(current_thread()); } int sched_yield() { bool sleeping = current_thread()->flags & THREAD_FLAGS_SLEEP; bool old = sleeping ? current_thread()->sleepintstatus : interrupt_set(false); + + sched_thread_stopping_callback(current_thread(), sleeping); arch_context_saveandcall(yield, current_cpu()->schedulerstack, NULL); + __assert(current_cpu()->ipl == IPL_NORMAL); interrupt_set(old); return sleeping ? current_thread()->wakeupreason : 0; @@ -298,6 +306,8 @@ bool sched_wakeup(thread_t *thread, int reason) { thread->flags &= ~(THREAD_FLAGS_SLEEP | THREAD_FLAGS_INTERRUPTIBLE); thread->wakeupreason = reason; + sched_thread_wakeup_callback(thread); + // TODO send IPI to idle cores sched_queue(thread); spinlock_release(&thread->sleeplock); @@ -323,6 +333,7 @@ static void dopreempt() { } spinlock_release(&runqueuelock); + switch_thread(next); } @@ -334,6 +345,8 @@ static void preempt_dpc(context_t *context, dpcarg_t arg) { if (current->flags & THREAD_FLAGS_PREEMPTED) return; + sched_thread_stopping_callback(current, false); + current->flags |= THREAD_FLAGS_PREEMPTED; ARCH_CONTEXT_THREADSAVE(current, context); @@ -390,6 +403,7 @@ static void reschedule_yield(context_t *context, void *_cpu) { switch_thread(next); } +// TODO verify if something like this is really needed void sched_reschedule_on_cpu(cpu_t *cpu, bool target) { cpu_t *old_target = current_thread()->cputarget; current_thread()->cputarget = cpu; @@ -403,6 +417,8 @@ void sched_reschedule_on_cpu(cpu_t *cpu, bool target) { long old_priority = current_thread()->priority; current_thread()->priority = 0; + sched_thread_stopping_callback(current_thread(), false); + arch_context_saveandcall(reschedule_yield, current_cpu()->schedulerstack, cpu); current_thread()->priority = old_priority; @@ -461,6 +477,8 @@ void sched_init() { sched_target_cpu(current_cpu()); + sched_thread_running_callback(current_thread()); + timer_insert(current_cpu()->timer, ¤t_cpu()->schedtimerentry, reschedule_timer_dpc, NULL, QUANTUM_US, true); // XXX move this resume to a more appropriate place timer_resume(current_cpu()->timer); diff --git a/kernel-src/sys/syscalls/fork.c b/kernel-src/sys/syscalls/fork.c index 14218d47..5d03e72a 100644 --- a/kernel-src/sys/syscalls/fork.c +++ b/kernel-src/sys/syscalls/fork.c @@ -4,6 +4,7 @@ #include #include #include +#include syscallret_t syscall_fork(context_t *ctx) { syscallret_t ret = { @@ -62,6 +63,12 @@ syscallret_t syscall_fork(context_t *ctx) { memcpy(&nproc->signals.pending, &proc->signals.pending, sizeof(sigset_t)); memcpy(&nproc->signals.actions, &proc->signals.actions[0], sizeof(sigaction_t) * NSIG); + // copy thread scheduling metrics over + // raised to IPL_DPC to prevent scheduling + long ipl = interrupt_raiseipl(IPL_DPC); + memcpy(&nthread->metrics, ¤t_thread()->metrics, sizeof(nthread->metrics)); + interrupt_loweripl(ipl); + ret.ret = nproc->pid; sched_queue(nthread); diff --git a/kernel-src/sys/thread.c b/kernel-src/sys/thread.c index 5258d019..0f5711c5 100644 --- a/kernel-src/sys/thread.c +++ b/kernel-src/sys/thread.c @@ -43,6 +43,8 @@ thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *pro SPINLOCK_INIT(thread->sleeplock); SPINLOCK_INIT(thread->signals.lock); EVENT_INITHEADER(&thread->signals.waitpendingevent); + memset(&thread->metrics, 0, sizeof(thread->metrics)); + thread->metrics.sleep_time_avg_us = 1; // to prevent a division by 0 when the thread first gets scheduled return thread; }