|
|
@ -0,0 +1,274 @@ |
|
|
|
#! /usr/bin/env python |
|
|
|
|
|
|
|
import sys |
|
|
|
from optparse import OptionParser |
|
|
|
import random |
|
|
|
|
|
|
|
# process switch behavior |
|
|
|
SCHED_SWITCH_ON_IO = 'SWITCH_ON_IO' |
|
|
|
|
|
|
|
# io finished behavior |
|
|
|
IO_RUN_LATER = 'IO_RUN_LATER' |
|
|
|
|
|
|
|
# process states |
|
|
|
STATE_RUNNING = 'RUNNING' |
|
|
|
STATE_READY = 'READY' |
|
|
|
STATE_DONE = 'DONE' |
|
|
|
STATE_WAIT = 'WAITING' |
|
|
|
|
|
|
|
# members of process structure |
|
|
|
PROC_CODE = 'code_' |
|
|
|
PROC_PC = 'pc_' |
|
|
|
PROC_ID = 'pid_' |
|
|
|
PROC_STATE = 'proc_state_' |
|
|
|
|
|
|
|
# things a process can do |
|
|
|
DO_COMPUTE = 'cpu' |
|
|
|
DO_YIELD = 'yld' |
|
|
|
DO_IO = 'io' |
|
|
|
|
|
|
|
class scheduler: |
|
|
|
def __init__(self, process_switch_behavior, io_done_behavior, io_length): |
|
|
|
# keep set of instructions for each of the processes |
|
|
|
self.proc_info = {} |
|
|
|
self.process_switch_behavior = process_switch_behavior |
|
|
|
self.io_done_behavior = io_done_behavior |
|
|
|
self.io_length = io_length |
|
|
|
return |
|
|
|
|
|
|
|
def new_process(self): |
|
|
|
proc_id = len(self.proc_info) |
|
|
|
self.proc_info[proc_id] = {} |
|
|
|
self.proc_info[proc_id][PROC_PC] = 0 |
|
|
|
self.proc_info[proc_id][PROC_ID] = proc_id |
|
|
|
self.proc_info[proc_id][PROC_CODE] = [] |
|
|
|
self.proc_info[proc_id][PROC_STATE] = STATE_READY |
|
|
|
return proc_id |
|
|
|
|
|
|
|
def load(self, program_description): |
|
|
|
proc_id = self.new_process() |
|
|
|
tmp = program_description.split(':') |
|
|
|
if len(tmp) != 3: |
|
|
|
print 'Bad description (%s): Must be number <x:y:z>' |
|
|
|
print ' where X is the number of instructions' |
|
|
|
print ' and Y is the percent change that an instruction is YIELD' |
|
|
|
print ' and Z is the percent change that an instruction is IO' |
|
|
|
exit(1) |
|
|
|
|
|
|
|
num_instructions, chance_yield, chance_io = int(tmp[0]), float(tmp[1])/100.0, float(tmp[2])/100.0 |
|
|
|
assert(chance_yield+chance_io<1) |
|
|
|
|
|
|
|
#print "proc %d, num_instr %d, change_cpu %f" % (proc_id,num_instructions, chance_cpu) |
|
|
|
for i in range(num_instructions): |
|
|
|
randnum=random.random(); |
|
|
|
if randnum < (1.0-chance_yield-chance_io): |
|
|
|
self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE) |
|
|
|
elif randnum >= (1.0-chance_yield-chance_io) and randnum < (1.0-chance_io): |
|
|
|
self.proc_info[proc_id][PROC_CODE].append(DO_YIELD) |
|
|
|
else: |
|
|
|
self.proc_info[proc_id][PROC_CODE].append(DO_IO) |
|
|
|
#print "proc %d, instr idx %d, instr cxt %s" % (proc_id, i, self.proc_info[proc_id][PROC_CODE][i]) |
|
|
|
return |
|
|
|
|
|
|
|
#change to READY STATE, the current proc's state should be expected |
|
|
|
#if pid==-1, then pid=self.curr_proc |
|
|
|
def move_to_ready(self, expected, pid=-1): |
|
|
|
#YOUR CODE |
|
|
|
return |
|
|
|
|
|
|
|
#change to RUNNING STATE, the current proc's state should be expected |
|
|
|
def move_to_running(self, expected): |
|
|
|
#YOUR CODE |
|
|
|
return |
|
|
|
|
|
|
|
#change to DONE STATE, the current proc's state should be expected |
|
|
|
def move_to_done(self, expected): |
|
|
|
#YOUR CODE |
|
|
|
return |
|
|
|
|
|
|
|
#choose next proc using FIFO/FCFS scheduling, If pid==-1, then pid=self.curr_proc |
|
|
|
def next_proc(self, pid=-1): |
|
|
|
#YOUR CODE |
|
|
|
return |
|
|
|
|
|
|
|
def get_num_processes(self): |
|
|
|
return len(self.proc_info) |
|
|
|
|
|
|
|
def get_num_instructions(self, pid): |
|
|
|
return len(self.proc_info[pid][PROC_CODE]) |
|
|
|
|
|
|
|
def get_instruction(self, pid, index): |
|
|
|
return self.proc_info[pid][PROC_CODE][index] |
|
|
|
|
|
|
|
def get_num_active(self): |
|
|
|
num_active = 0 |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
if self.proc_info[pid][PROC_STATE] != STATE_DONE: |
|
|
|
num_active += 1 |
|
|
|
return num_active |
|
|
|
|
|
|
|
def get_num_runnable(self): |
|
|
|
num_active = 0 |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
if self.proc_info[pid][PROC_STATE] == STATE_READY or \ |
|
|
|
self.proc_info[pid][PROC_STATE] == STATE_RUNNING: |
|
|
|
num_active += 1 |
|
|
|
return num_active |
|
|
|
|
|
|
|
def get_ios_in_flight(self, current_time): |
|
|
|
num_in_flight = 0 |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
for t in self.io_finish_times[pid]: |
|
|
|
if t > current_time: |
|
|
|
num_in_flight += 1 |
|
|
|
return num_in_flight |
|
|
|
|
|
|
|
|
|
|
|
def space(self, num_columns): |
|
|
|
for i in range(num_columns): |
|
|
|
print '%10s' % ' ', |
|
|
|
|
|
|
|
def check_if_done(self): |
|
|
|
if len(self.proc_info[self.curr_proc][PROC_CODE]) == 0: |
|
|
|
if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING: |
|
|
|
self.move_to_done(STATE_RUNNING) |
|
|
|
self.next_proc() |
|
|
|
return |
|
|
|
|
|
|
|
def run(self): |
|
|
|
clock_tick = 0 |
|
|
|
|
|
|
|
if len(self.proc_info) == 0: |
|
|
|
return |
|
|
|
|
|
|
|
# track outstanding IOs, per process |
|
|
|
self.io_finish_times = {} |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
self.io_finish_times[pid] = [] |
|
|
|
|
|
|
|
# make first one active |
|
|
|
self.curr_proc = 0 |
|
|
|
self.move_to_running(STATE_READY) |
|
|
|
|
|
|
|
# OUTPUT: heade`[rs for each column |
|
|
|
print '%s' % 'Time', |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
print '%10s' % ('PID:%2d' % (pid)), |
|
|
|
print '%10s' % 'CPU', |
|
|
|
print '%10s' % 'IOs', |
|
|
|
print '' |
|
|
|
|
|
|
|
# init statistics |
|
|
|
io_busy = 0 |
|
|
|
cpu_busy = 0 |
|
|
|
|
|
|
|
while self.get_num_active() > 0: |
|
|
|
clock_tick += 1 |
|
|
|
|
|
|
|
# check for io finish |
|
|
|
io_done = False |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
if clock_tick in self.io_finish_times[pid]: |
|
|
|
# if IO finished, the should do something for related process |
|
|
|
#YOUR CODE |
|
|
|
pass #YOU should delete this |
|
|
|
|
|
|
|
# if current proc is RUNNING and has an instruction, execute it |
|
|
|
instruction_to_execute = '' |
|
|
|
if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING and \ |
|
|
|
len(self.proc_info[self.curr_proc][PROC_CODE]) > 0: |
|
|
|
#pop a instruction from proc_info[self.curr_proc][PROC_CODE]to instruction_to_execute |
|
|
|
#YOUR CODE |
|
|
|
pass #YOU should delete this |
|
|
|
|
|
|
|
# OUTPUT: print what everyone is up to |
|
|
|
if io_done: |
|
|
|
print '%3d*' % clock_tick, |
|
|
|
else: |
|
|
|
print '%3d ' % clock_tick, |
|
|
|
for pid in range(len(self.proc_info)): |
|
|
|
if pid == self.curr_proc and instruction_to_execute != '': |
|
|
|
print '%10s' % ('RUN:'+instruction_to_execute), |
|
|
|
else: |
|
|
|
print '%10s' % (self.proc_info[pid][PROC_STATE]), |
|
|
|
if instruction_to_execute == '': |
|
|
|
print '%10s' % ' ', |
|
|
|
else: |
|
|
|
print '%10s' % 1, |
|
|
|
num_outstanding = self.get_ios_in_flight(clock_tick) |
|
|
|
if num_outstanding > 0: |
|
|
|
print '%10s' % str(num_outstanding), |
|
|
|
io_busy += 1 |
|
|
|
else: |
|
|
|
print '%10s' % ' ', |
|
|
|
print '' |
|
|
|
|
|
|
|
# if this is an YIELD instruction, switch to ready state |
|
|
|
# and add an io completion in the future |
|
|
|
if instruction_to_execute == DO_YIELD: |
|
|
|
#YOUR CODE |
|
|
|
pass #YOU should delete this |
|
|
|
# if this is an IO instruction, switch to waiting state |
|
|
|
# and add an io completion in the future |
|
|
|
elif instruction_to_execute == DO_IO: |
|
|
|
#YOUR CODE |
|
|
|
pass #YOU should delete this |
|
|
|
|
|
|
|
# ENDCASE: check if currently running thing is out of instructions |
|
|
|
self.check_if_done() |
|
|
|
return (cpu_busy, io_busy, clock_tick) |
|
|
|
|
|
|
|
# |
|
|
|
# PARSE ARGUMENTS |
|
|
|
# |
|
|
|
|
|
|
|
parser = OptionParser() |
|
|
|
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed') |
|
|
|
parser.add_option('-l', '--processlist', default='', |
|
|
|
help='a comma-separated list of processes to run, in the form X1:Y1:Z1,X2:Y2:Z2,... where X is the number of instructions that process should run, and Y/Z the chances (from 0 to 100) issue an YIELD/IO', |
|
|
|
action='store', type='string', dest='process_list') |
|
|
|
parser.add_option('-L', '--iolength', default=3, help='how long an IO takes', action='store', type='int', dest='io_length') |
|
|
|
parser.add_option('-p', '--printstats', help='print statistics at end; only useful with -c flag (otherwise stats are not printed)', action='store_true', default=False, dest='print_stats') |
|
|
|
(options, args) = parser.parse_args() |
|
|
|
|
|
|
|
random.seed(options.seed) |
|
|
|
|
|
|
|
process_switch_behavior = SCHED_SWITCH_ON_IO |
|
|
|
io_done_behavior = IO_RUN_LATER |
|
|
|
io_length=options.io_length |
|
|
|
|
|
|
|
|
|
|
|
s = scheduler(process_switch_behavior, io_done_behavior, io_length) |
|
|
|
|
|
|
|
# example process description (10:100,10:100) |
|
|
|
for p in options.process_list.split(','): |
|
|
|
s.load(p) |
|
|
|
|
|
|
|
print 'Produce a trace of what would happen when you run these processes:' |
|
|
|
for pid in range(s.get_num_processes()): |
|
|
|
print 'Process %d' % pid |
|
|
|
for inst in range(s.get_num_instructions(pid)): |
|
|
|
print ' %s' % s.get_instruction(pid, inst) |
|
|
|
print '' |
|
|
|
print 'Important behaviors:' |
|
|
|
print ' System will switch when', |
|
|
|
if process_switch_behavior == SCHED_SWITCH_ON_IO: |
|
|
|
print 'the current process is FINISHED or ISSUES AN YIELD or IO' |
|
|
|
else: |
|
|
|
print 'error in sched switch on iobehavior' |
|
|
|
exit (-1) |
|
|
|
print ' After IOs, the process issuing the IO will', |
|
|
|
if io_done_behavior == IO_RUN_LATER: |
|
|
|
print 'run LATER (when it is its turn)' |
|
|
|
else: |
|
|
|
print 'error in IO done behavior' |
|
|
|
exit (-1) |
|
|
|
print '' |
|
|
|
|
|
|
|
(cpu_busy, io_busy, clock_tick) = s.run() |
|
|
|
|
|
|
|
print '' |
|
|
|
print 'Stats: Total Time %d' % clock_tick |
|
|
|
print 'Stats: CPU Busy %d (%.2f%%)' % (cpu_busy, 100.0 * float(cpu_busy)/clock_tick) |
|
|
|
print 'Stats: IO Busy %d (%.2f%%)' % (io_busy, 100.0 * float(io_busy)/clock_tick) |
|
|
|
print '' |