#include #include #include #include "scheduler.h" #include "semaphore.h" #include "rnd_dist.h" #include "perfetto-bridge.h" /* A simulate thread is a subclass of a simulation process */ struct thread { struct process super; // a random variable describing the time spent holding the lock struct urnd inside_lock_rand; // a random variable describing the time spent not holding the lock struct urnd outside_lock_rand; // a lock this thread struct semaphore *lock; }; long SEED = 13; long SIM_TIME = 200; /* 3 simulated locks. */ static struct semaphore *lock1; static struct semaphore *lock2; static struct semaphore *lock3; static void simulated_thread(struct process *p) { struct thread *self = (struct thread *)p; struct scheduler *sched __attribute__((__unused__)) = p->scheduler; int total_blocked = 0; int total_running = 0; /* Simulate this thread's execution until the scheduler's clock * passes SIM_TIME * * A simulated thread is modeled as follows: * - acquire the associated lock (here, we use simulated semaphores) * - once the lock was acquired, * run for a random amount of time drawn from inside_lock_rand * - release the associated lock * - run for a random amount of time drawn from outside_lock_rand * - repeat * * finally, report the CPU utilization (assuming that this simulated * thread had a dedicated CPU the entire time). * * Use perfetto_trace_event_begin and perfetto_trace_event_end to output * begin/end marker for trace events that can be visualized in Perfetto */ printf("thread %s blocked for %d and running for %d units, utilization %.2f%%\n", self->super.name, total_blocked, total_running, 100.0 * total_running/(total_running+total_blocked)); } /* * Create a new simulated thread */ static struct process * thread_create(struct scheduler *sched, const char *name, struct semaphore *lock, int inside_lo, int inside_hi, int outside_lo, int outside_hi) { struct thread *job = calloc(1, sizeof(*job)); urnd_init(&job->inside_lock_rand, inside_lo, inside_hi, SEED); urnd_init(&job->outside_lock_rand, outside_lo, outside_hi, SEED); job->lock = lock; process_init(&job->super, name, sched, simulated_thread); return &job->super; } static void main_impl(void) { char *seed = getenv("SEED"); if (seed != NULL) SEED = atol(seed); struct scheduler *sched = sched_create(); lock1 = semaphore_new(sched, "Lock 1"); lock2 = semaphore_new(sched, "Lock 2 "); lock3 = semaphore_new(sched, "Lock 3 "); /* An example scenario where 4 threads hold a single lock for 1 time unit, * then run for 10 time units without the lock, repeatedly. */ sched_activate_now(sched, thread_create(sched, "T1", lock1, 1, 1, 10, 10)); sched_activate_now(sched, thread_create(sched, "T2", lock1, 1, 1, 10, 10)); sched_activate_now(sched, thread_create(sched, "T3", lock1, 1, 1, 10, 10)); sched_activate_now(sched, thread_create(sched, "T4", lock1, 1, 1, 10, 10)); sched_run_simulation(sched); } int main() { perfetto_init(); /* This file can be visualized at ui.perfetto.dev */ perfetto_trace(main_impl, "lock.pftrace"); }