|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+#! /usr/bin/env python
|
|
|
2
|
+
|
|
|
3
|
+import sys
|
|
|
4
|
+from optparse import OptionParser
|
|
|
5
|
+import random
|
|
|
6
|
+
|
|
|
7
|
+# process switch behavior
|
|
|
8
|
+SCHED_SWITCH_ON_IO = 'SWITCH_ON_IO'
|
|
|
9
|
+SCHED_SWITCH_ON_END = 'SWITCH_ON_END'
|
|
|
10
|
+
|
|
|
11
|
+# io finished behavior
|
|
|
12
|
+IO_RUN_LATER = 'IO_RUN_LATER'
|
|
|
13
|
+IO_RUN_IMMEDIATE = 'IO_RUN_IMMEDIATE'
|
|
|
14
|
+
|
|
|
15
|
+# process states
|
|
|
16
|
+STATE_RUNNING = 'RUNNING'
|
|
|
17
|
+STATE_READY = 'READY'
|
|
|
18
|
+STATE_DONE = 'DONE'
|
|
|
19
|
+STATE_WAIT = 'WAITING'
|
|
|
20
|
+
|
|
|
21
|
+# members of process structure
|
|
|
22
|
+PROC_CODE = 'code_'
|
|
|
23
|
+PROC_PC = 'pc_'
|
|
|
24
|
+PROC_ID = 'pid_'
|
|
|
25
|
+PROC_STATE = 'proc_state_'
|
|
|
26
|
+
|
|
|
27
|
+# things a process can do
|
|
|
28
|
+DO_COMPUTE = 'cpu'
|
|
|
29
|
+DO_IO = 'io'
|
|
|
30
|
+
|
|
|
31
|
+
|
|
|
32
|
+class scheduler:
|
|
|
33
|
+ def __init__(self, process_switch_behavior, io_done_behavior, io_length):
|
|
|
34
|
+ # keep set of instructions for each of the processes
|
|
|
35
|
+ self.proc_info = {}
|
|
|
36
|
+ self.process_switch_behavior = process_switch_behavior
|
|
|
37
|
+ self.io_done_behavior = io_done_behavior
|
|
|
38
|
+ self.io_length = io_length
|
|
|
39
|
+ return
|
|
|
40
|
+
|
|
|
41
|
+ def new_process(self):
|
|
|
42
|
+ proc_id = len(self.proc_info)
|
|
|
43
|
+ self.proc_info[proc_id] = {}
|
|
|
44
|
+ self.proc_info[proc_id][PROC_PC] = 0
|
|
|
45
|
+ self.proc_info[proc_id][PROC_ID] = proc_id
|
|
|
46
|
+ self.proc_info[proc_id][PROC_CODE] = []
|
|
|
47
|
+ self.proc_info[proc_id][PROC_STATE] = STATE_READY
|
|
|
48
|
+ return proc_id
|
|
|
49
|
+
|
|
|
50
|
+ def load_file(self, progfile):
|
|
|
51
|
+ fd = open(progfile)
|
|
|
52
|
+ proc_id = self.new_process()
|
|
|
53
|
+
|
|
|
54
|
+ for line in fd:
|
|
|
55
|
+ tmp = line.split()
|
|
|
56
|
+ if len(tmp) == 0:
|
|
|
57
|
+ continue
|
|
|
58
|
+ opcode = tmp[0]
|
|
|
59
|
+ if opcode == 'compute':
|
|
|
60
|
+ assert(len(tmp) == 2)
|
|
|
61
|
+ for i in range(int(tmp[1])):
|
|
|
62
|
+ self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
|
|
|
63
|
+ elif opcode == 'io':
|
|
|
64
|
+ assert(len(tmp) == 1)
|
|
|
65
|
+ self.proc_info[proc_id][PROC_CODE].append(DO_IO)
|
|
|
66
|
+ fd.close()
|
|
|
67
|
+ return
|
|
|
68
|
+
|
|
|
69
|
+ def load(self, program_description):
|
|
|
70
|
+ proc_id = self.new_process()
|
|
|
71
|
+ tmp = program_description.split(':')
|
|
|
72
|
+ if len(tmp) != 2:
|
|
|
73
|
+ print 'Bad description (%s): Must be number <x:y>' % program_description
|
|
|
74
|
+ print ' where X is the number of instructions'
|
|
|
75
|
+ print ' and Y is the percent change that an instruction is CPU not IO'
|
|
|
76
|
+ exit(1)
|
|
|
77
|
+
|
|
|
78
|
+ num_instructions, chance_cpu = int(tmp[0]), float(tmp[1])/100.0
|
|
|
79
|
+ for i in range(num_instructions):
|
|
|
80
|
+ if random.random() < chance_cpu:
|
|
|
81
|
+ self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
|
|
|
82
|
+ else:
|
|
|
83
|
+ self.proc_info[proc_id][PROC_CODE].append(DO_IO)
|
|
|
84
|
+ return
|
|
|
85
|
+
|
|
|
86
|
+ def move_to_ready(self, expected, pid=-1):
|
|
|
87
|
+ if pid == -1:
|
|
|
88
|
+ pid = self.curr_proc
|
|
|
89
|
+ assert(self.proc_info[pid][PROC_STATE] == expected)
|
|
|
90
|
+ self.proc_info[pid][PROC_STATE] = STATE_READY
|
|
|
91
|
+ return
|
|
|
92
|
+
|
|
|
93
|
+ def move_to_wait(self, expected):
|
|
|
94
|
+ assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
|
|
95
|
+ self.proc_info[self.curr_proc][PROC_STATE] = STATE_WAIT
|
|
|
96
|
+ return
|
|
|
97
|
+
|
|
|
98
|
+ def move_to_running(self, expected):
|
|
|
99
|
+ assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
|
|
100
|
+ self.proc_info[self.curr_proc][PROC_STATE] = STATE_RUNNING
|
|
|
101
|
+ return
|
|
|
102
|
+
|
|
|
103
|
+ def move_to_done(self, expected):
|
|
|
104
|
+ assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
|
|
105
|
+ self.proc_info[self.curr_proc][PROC_STATE] = STATE_DONE
|
|
|
106
|
+ return
|
|
|
107
|
+
|
|
|
108
|
+ def next_proc(self, pid=-1):
|
|
|
109
|
+ if pid != -1:
|
|
|
110
|
+ self.curr_proc = pid
|
|
|
111
|
+ self.move_to_running(STATE_READY)
|
|
|
112
|
+ return
|
|
|
113
|
+ for pid in range(self.curr_proc + 1, len(self.proc_info)):
|
|
|
114
|
+ if self.proc_info[pid][PROC_STATE] == STATE_READY:
|
|
|
115
|
+ self.curr_proc = pid
|
|
|
116
|
+ self.move_to_running(STATE_READY)
|
|
|
117
|
+ return
|
|
|
118
|
+ for pid in range(0, self.curr_proc + 1):
|
|
|
119
|
+ if self.proc_info[pid][PROC_STATE] == STATE_READY:
|
|
|
120
|
+ self.curr_proc = pid
|
|
|
121
|
+ self.move_to_running(STATE_READY)
|
|
|
122
|
+ return
|
|
|
123
|
+ return
|
|
|
124
|
+
|
|
|
125
|
+ def get_num_processes(self):
|
|
|
126
|
+ return len(self.proc_info)
|
|
|
127
|
+
|
|
|
128
|
+ def get_num_instructions(self, pid):
|
|
|
129
|
+ return len(self.proc_info[pid][PROC_CODE])
|
|
|
130
|
+
|
|
|
131
|
+ def get_instruction(self, pid, index):
|
|
|
132
|
+ return self.proc_info[pid][PROC_CODE][index]
|
|
|
133
|
+
|
|
|
134
|
+ def get_num_active(self):
|
|
|
135
|
+ num_active = 0
|
|
|
136
|
+ for pid in range(len(self.proc_info)):
|
|
|
137
|
+ if self.proc_info[pid][PROC_STATE] != STATE_DONE:
|
|
|
138
|
+ num_active += 1
|
|
|
139
|
+ return num_active
|
|
|
140
|
+
|
|
|
141
|
+ def get_num_runnable(self):
|
|
|
142
|
+ num_active = 0
|
|
|
143
|
+ for pid in range(len(self.proc_info)):
|
|
|
144
|
+ if self.proc_info[pid][PROC_STATE] == STATE_READY or \
|
|
|
145
|
+ self.proc_info[pid][PROC_STATE] == STATE_RUNNING:
|
|
|
146
|
+ num_active += 1
|
|
|
147
|
+ return num_active
|
|
|
148
|
+
|
|
|
149
|
+ def get_ios_in_flight(self, current_time):
|
|
|
150
|
+ num_in_flight = 0
|
|
|
151
|
+ for pid in range(len(self.proc_info)):
|
|
|
152
|
+ for t in self.io_finish_times[pid]:
|
|
|
153
|
+ if t > current_time:
|
|
|
154
|
+ num_in_flight += 1
|
|
|
155
|
+ return num_in_flight
|
|
|
156
|
+
|
|
|
157
|
+ def check_for_switch(self):
|
|
|
158
|
+ return
|
|
|
159
|
+
|
|
|
160
|
+ def space(self, num_columns):
|
|
|
161
|
+ for i in range(num_columns):
|
|
|
162
|
+ print '%10s' % ' ',
|
|
|
163
|
+
|
|
|
164
|
+ def check_if_done(self):
|
|
|
165
|
+ if len(self.proc_info[self.curr_proc][PROC_CODE]) == 0:
|
|
|
166
|
+ if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
|
|
|
167
|
+ self.move_to_done(STATE_RUNNING)
|
|
|
168
|
+ self.next_proc()
|
|
|
169
|
+ return
|
|
|
170
|
+
|
|
|
171
|
+ def run(self):
|
|
|
172
|
+ clock_tick = 0
|
|
|
173
|
+
|
|
|
174
|
+ if len(self.proc_info) == 0:
|
|
|
175
|
+ return
|
|
|
176
|
+
|
|
|
177
|
+ # track outstanding IOs, per process
|
|
|
178
|
+ self.io_finish_times = {}
|
|
|
179
|
+ for pid in range(len(self.proc_info)):
|
|
|
180
|
+ self.io_finish_times[pid] = []
|
|
|
181
|
+
|
|
|
182
|
+ # make first one active
|
|
|
183
|
+ self.curr_proc = 0
|
|
|
184
|
+ self.move_to_running(STATE_READY)
|
|
|
185
|
+
|
|
|
186
|
+ # OUTPUT: headers for each column
|
|
|
187
|
+ print '%s' % 'Time',
|
|
|
188
|
+ for pid in range(len(self.proc_info)):
|
|
|
189
|
+ print '%10s' % ('PID:%2d' % (pid)),
|
|
|
190
|
+ print '%10s' % 'CPU',
|
|
|
191
|
+ print '%10s' % 'IOs',
|
|
|
192
|
+ print ''
|
|
|
193
|
+
|
|
|
194
|
+ # init statistics
|
|
|
195
|
+ io_busy = 0
|
|
|
196
|
+ cpu_busy = 0
|
|
|
197
|
+
|
|
|
198
|
+ while self.get_num_active() > 0:
|
|
|
199
|
+ clock_tick += 1
|
|
|
200
|
+
|
|
|
201
|
+ # check for io finish
|
|
|
202
|
+ io_done = False
|
|
|
203
|
+ for pid in range(len(self.proc_info)):
|
|
|
204
|
+ if clock_tick in self.io_finish_times[pid]:
|
|
|
205
|
+ io_done = True
|
|
|
206
|
+ self.move_to_ready(STATE_WAIT, pid)
|
|
|
207
|
+ if self.io_done_behavior == IO_RUN_IMMEDIATE:
|
|
|
208
|
+ # IO_RUN_IMMEDIATE
|
|
|
209
|
+ if self.curr_proc != pid:
|
|
|
210
|
+ if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
|
|
|
211
|
+ self.move_to_ready(STATE_RUNNING)
|
|
|
212
|
+ self.next_proc(pid)
|
|
|
213
|
+ else:
|
|
|
214
|
+ # IO_RUN_LATER
|
|
|
215
|
+ if self.process_switch_behavior == SCHED_SWITCH_ON_END and self.get_num_runnable() > 1:
|
|
|
216
|
+ # this means the process that issued the io should be run
|
|
|
217
|
+ self.next_proc(pid)
|
|
|
218
|
+ if self.get_num_runnable() == 1:
|
|
|
219
|
+ # this is the only thing to run: so run it
|
|
|
220
|
+ self.next_proc(pid)
|
|
|
221
|
+ self.check_if_done()
|
|
|
222
|
+
|
|
|
223
|
+ # if current proc is RUNNING and has an instruction, execute it
|
|
|
224
|
+ instruction_to_execute = ''
|
|
|
225
|
+ if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING and \
|
|
|
226
|
+ len(self.proc_info[self.curr_proc][PROC_CODE]) > 0:
|
|
|
227
|
+ instruction_to_execute = self.proc_info[self.curr_proc][PROC_CODE].pop(0)
|
|
|
228
|
+ cpu_busy += 1
|
|
|
229
|
+
|
|
|
230
|
+ # OUTPUT: print what everyone is up to
|
|
|
231
|
+ if io_done:
|
|
|
232
|
+ print '%3d*' % clock_tick,
|
|
|
233
|
+ else:
|
|
|
234
|
+ print '%3d ' % clock_tick,
|
|
|
235
|
+ for pid in range(len(self.proc_info)):
|
|
|
236
|
+ if pid == self.curr_proc and instruction_to_execute != '':
|
|
|
237
|
+ print '%10s' % ('RUN:'+instruction_to_execute),
|
|
|
238
|
+ else:
|
|
|
239
|
+ print '%10s' % (self.proc_info[pid][PROC_STATE]),
|
|
|
240
|
+ if instruction_to_execute == '':
|
|
|
241
|
+ print '%10s' % ' ',
|
|
|
242
|
+ else:
|
|
|
243
|
+ print '%10s' % 1,
|
|
|
244
|
+ num_outstanding = self.get_ios_in_flight(clock_tick)
|
|
|
245
|
+ if num_outstanding > 0:
|
|
|
246
|
+ print '%10s' % str(num_outstanding),
|
|
|
247
|
+ io_busy += 1
|
|
|
248
|
+ else:
|
|
|
249
|
+ print '%10s' % ' ',
|
|
|
250
|
+ print ''
|
|
|
251
|
+
|
|
|
252
|
+ # if this is an IO instruction, switch to waiting state
|
|
|
253
|
+ # and add an io completion in the future
|
|
|
254
|
+ if instruction_to_execute == DO_IO:
|
|
|
255
|
+ self.move_to_wait(STATE_RUNNING)
|
|
|
256
|
+ self.io_finish_times[self.curr_proc].append(clock_tick + self.io_length)
|
|
|
257
|
+ if self.process_switch_behavior == SCHED_SWITCH_ON_IO:
|
|
|
258
|
+ self.next_proc()
|
|
|
259
|
+
|
|
|
260
|
+ # ENDCASE: check if currently running thing is out of instructions
|
|
|
261
|
+ self.check_if_done()
|
|
|
262
|
+ return (cpu_busy, io_busy, clock_tick)
|
|
|
263
|
+
|
|
|
264
|
+#
|
|
|
265
|
+# PARSE ARGUMENTS
|
|
|
266
|
+#
|
|
|
267
|
+
|
|
|
268
|
+parser = OptionParser()
|
|
|
269
|
+parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
|
|
270
|
+parser.add_option('-l', '--processlist', default='',
|
|
|
271
|
+ help='a comma-separated list of processes to run, in the form X1:Y1,X2:Y2,... where X is the number of instructions that process should run, and Y the chances (from 0 to 100) that an instruction will use the CPU or issue an IO',
|
|
|
272
|
+ action='store', type='string', dest='process_list')
|
|
|
273
|
+parser.add_option('-L', '--iolength', default=5, help='how long an IO takes', action='store', type='int', dest='io_length')
|
|
|
274
|
+parser.add_option('-S', '--switch', default='SWITCH_ON_IO',
|
|
|
275
|
+ help='when to switch between processes: SWITCH_ON_IO, SWITCH_ON_END',
|
|
|
276
|
+ action='store', type='string', dest='process_switch_behavior')
|
|
|
277
|
+parser.add_option('-I', '--iodone', default='IO_RUN_LATER',
|
|
|
278
|
+ help='type of behavior when IO ends: IO_RUN_LATER, IO_RUN_IMMEDIATE',
|
|
|
279
|
+ action='store', type='string', dest='io_done_behavior')
|
|
|
280
|
+parser.add_option('-c', help='compute answers for me', action='store_true', default=False, dest='solve')
|
|
|
281
|
+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')
|
|
|
282
|
+(options, args) = parser.parse_args()
|
|
|
283
|
+
|
|
|
284
|
+random.seed(options.seed)
|
|
|
285
|
+
|
|
|
286
|
+assert(options.process_switch_behavior == SCHED_SWITCH_ON_IO or \
|
|
|
287
|
+ options.process_switch_behavior == SCHED_SWITCH_ON_END)
|
|
|
288
|
+assert(options.io_done_behavior == IO_RUN_IMMEDIATE or \
|
|
|
289
|
+ options.io_done_behavior == IO_RUN_LATER)
|
|
|
290
|
+
|
|
|
291
|
+s = scheduler(options.process_switch_behavior, options.io_done_behavior, options.io_length)
|
|
|
292
|
+
|
|
|
293
|
+# example process description (10:100,10:100)
|
|
|
294
|
+for p in options.process_list.split(','):
|
|
|
295
|
+ s.load(p)
|
|
|
296
|
+
|
|
|
297
|
+if options.solve == False:
|
|
|
298
|
+ print 'Produce a trace of what would happen when you run these processes:'
|
|
|
299
|
+ for pid in range(s.get_num_processes()):
|
|
|
300
|
+ print 'Process %d' % pid
|
|
|
301
|
+ for inst in range(s.get_num_instructions(pid)):
|
|
|
302
|
+ print ' %s' % s.get_instruction(pid, inst)
|
|
|
303
|
+ print ''
|
|
|
304
|
+ print 'Important behaviors:'
|
|
|
305
|
+ print ' System will switch when',
|
|
|
306
|
+ if options.process_switch_behavior == SCHED_SWITCH_ON_IO:
|
|
|
307
|
+ print 'the current process is FINISHED or ISSUES AN IO'
|
|
|
308
|
+ else:
|
|
|
309
|
+ print 'the current process is FINISHED'
|
|
|
310
|
+ print ' After IOs, the process issuing the IO will',
|
|
|
311
|
+ if options.io_done_behavior == IO_RUN_IMMEDIATE:
|
|
|
312
|
+ print 'run IMMEDIATELY'
|
|
|
313
|
+ else:
|
|
|
314
|
+ print 'run LATER (when it is its turn)'
|
|
|
315
|
+ print ''
|
|
|
316
|
+ exit(0)
|
|
|
317
|
+
|
|
|
318
|
+(cpu_busy, io_busy, clock_tick) = s.run()
|
|
|
319
|
+
|
|
|
320
|
+if options.print_stats:
|
|
|
321
|
+ print ''
|
|
|
322
|
+ print 'Stats: Total Time %d' % clock_tick
|
|
|
323
|
+ print 'Stats: CPU Busy %d (%.2f%%)' % (cpu_busy, 100.0 * float(cpu_busy)/clock_tick)
|
|
|
324
|
+ print 'Stats: IO Busy %d (%.2f%%)' % (io_busy, 100.0 * float(io_busy)/clock_tick)
|
|
|
325
|
+ print ''
|