|
|
@ -0,0 +1,719 @@ |
|
|
|
#! /usr/bin/env python |
|
|
|
|
|
|
|
from Tkinter import * |
|
|
|
from types import * |
|
|
|
import math, random, time, sys, os |
|
|
|
from optparse import OptionParser |
|
|
|
from decimal import * |
|
|
|
|
|
|
|
MAXTRACKS = 1000 |
|
|
|
|
|
|
|
# states that a request/disk go through |
|
|
|
STATE_NULL = 0 |
|
|
|
STATE_SEEK = 1 |
|
|
|
STATE_ROTATE = 2 |
|
|
|
STATE_XFER = 3 |
|
|
|
STATE_DONE = 4 |
|
|
|
|
|
|
|
# |
|
|
|
# TODO |
|
|
|
# XXX transfer time |
|
|
|
# XXX satf |
|
|
|
# XXX skew |
|
|
|
# XXX scheduling window |
|
|
|
# XXX sstf |
|
|
|
# XXX specify requests vs. random requests in range |
|
|
|
# XXX add new requests as old ones complete (starvation) |
|
|
|
# XXX run in non-graphical mode |
|
|
|
# XXX better graphical display (show key, long lists of requests, more timings on screen) |
|
|
|
# XXX be able to do "pure" sequential |
|
|
|
# XXX add more blocks around outer tracks (zoning) |
|
|
|
# XXX simple flag to make scheduling window a fairness window (-F) |
|
|
|
# new algs to scan and c-scan the disk? |
|
|
|
# |
|
|
|
|
|
|
|
class Disk: |
|
|
|
def __init__(self, addr, addrDesc, lateAddr, lateAddrDesc, |
|
|
|
policy, seekSpeed, rotateSpeed, skew, window, compute, |
|
|
|
graphics, zoning): |
|
|
|
self.addr = addr |
|
|
|
self.addrDesc = addrDesc |
|
|
|
self.lateAddr = lateAddr |
|
|
|
self.lateAddrDesc = lateAddrDesc |
|
|
|
self.policy = policy |
|
|
|
self.seekSpeed = Decimal(seekSpeed) |
|
|
|
self.rotateSpeed = Decimal(rotateSpeed) |
|
|
|
self.skew = skew |
|
|
|
self.window = window |
|
|
|
self.compute = compute |
|
|
|
self.graphics = graphics |
|
|
|
self.zoning = zoning |
|
|
|
|
|
|
|
# figure out zones first, to figure out the max possible request |
|
|
|
self.InitBlockLayout() |
|
|
|
|
|
|
|
# figure out requests |
|
|
|
random.seed(options.seed) |
|
|
|
self.requests = self.MakeRequests(self.addr, self.addrDesc) |
|
|
|
self.lateRequests = self.MakeRequests(self.lateAddr, self.lateAddrDesc) |
|
|
|
|
|
|
|
# graphical startup |
|
|
|
self.width = 500 |
|
|
|
if self.graphics: |
|
|
|
self.root = Tk() |
|
|
|
tmpLen = len(self.requests) |
|
|
|
if len(self.lateRequests) > 0: |
|
|
|
tmpLen += len(self.lateRequests) |
|
|
|
self.canvas = Canvas(self.root, width=410, height=460 + ((tmpLen / 20) * 20)) |
|
|
|
self.canvas.pack() |
|
|
|
|
|
|
|
# fairness stuff |
|
|
|
if self.policy == 'BSATF' and self.window != -1: |
|
|
|
self.fairWindow = self.window |
|
|
|
else: |
|
|
|
self.fairWindow = -1 |
|
|
|
|
|
|
|
print 'REQUESTS', self.requests |
|
|
|
print '' |
|
|
|
|
|
|
|
# for late requests |
|
|
|
self.lateCount = 0 |
|
|
|
if len(self.lateRequests) > 0: |
|
|
|
print 'LATE REQUESTS', self.lateRequests |
|
|
|
print '' |
|
|
|
|
|
|
|
if self.compute == False: |
|
|
|
print '' |
|
|
|
print 'For the requests above, compute the seek, rotate, and transfer times.' |
|
|
|
print 'Use -c or the graphical mode (-G) to see the answers.' |
|
|
|
print '' |
|
|
|
|
|
|
|
# BINDINGS |
|
|
|
if self.graphics: |
|
|
|
self.root.bind('s', self.Start) |
|
|
|
self.root.bind('p', self.Pause) |
|
|
|
self.root.bind('q', self.Exit) |
|
|
|
|
|
|
|
# TRACK INFO |
|
|
|
self.tracks = {} |
|
|
|
self.trackWidth = 40 |
|
|
|
self.tracks[0] = 140 |
|
|
|
self.tracks[1] = self.tracks[0] - self.trackWidth |
|
|
|
self.tracks[2] = self.tracks[1] - self.trackWidth |
|
|
|
|
|
|
|
if (self.seekSpeed > 1 and self.trackWidth % self.seekSpeed != 0): |
|
|
|
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth) |
|
|
|
sys.exit(1) |
|
|
|
if self.seekSpeed < 1: |
|
|
|
x = (self.trackWidth / self.seekSpeed) |
|
|
|
y = int(float(self.trackWidth) / float(self.seekSpeed)) |
|
|
|
if float(x) != float(y): |
|
|
|
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth) |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
# DISK SURFACE |
|
|
|
self.cx = self.width/2.0 |
|
|
|
self.cy = self.width/2.0 |
|
|
|
if self.graphics: |
|
|
|
self.canvas.create_rectangle(self.cx-175, 30, self.cx - 20, 80, fill='gray', outline='black') |
|
|
|
self.platterSize = 320 |
|
|
|
ps2 = self.platterSize / 2.0 |
|
|
|
if self.graphics: |
|
|
|
self.canvas.create_oval(self.cx-ps2, self.cy-ps2, self.cx+ps2, self.cy + ps2, fill='darkgray', outline='black') |
|
|
|
for i in range(len(self.tracks)): |
|
|
|
t = self.tracks[i] - (self.trackWidth / 2.0) |
|
|
|
if self.graphics: |
|
|
|
self.canvas.create_oval(self.cx - t, self.cy - t, self.cx + t, self.cy + t, fill='', outline='black', width=1.0) |
|
|
|
|
|
|
|
# SPINDLE |
|
|
|
self.spindleX = self.cx |
|
|
|
self.spindleY = self.cy |
|
|
|
if self.graphics: |
|
|
|
self.spindleID = self.canvas.create_oval(self.spindleX-3, self.spindleY-3, self.spindleX+3, self.spindleY+3, fill='orange', outline='black') |
|
|
|
|
|
|
|
# DISK ARM |
|
|
|
self.armTrack = 0 |
|
|
|
self.armSpeedBase = float(seekSpeed) |
|
|
|
self.armSpeed = float(seekSpeed) |
|
|
|
|
|
|
|
distFromSpindle = self.tracks[self.armTrack] |
|
|
|
self.armWidth = 20 |
|
|
|
self.headWidth = 10 |
|
|
|
|
|
|
|
self.armX = self.spindleX - (distFromSpindle * math.cos(math.radians(0))) |
|
|
|
self.armX1 = self.armX - self.armWidth |
|
|
|
self.armX2 = self.armX + self.armWidth |
|
|
|
self.armY1 = 50.0 |
|
|
|
self.armY2 = self.width / 2.0 |
|
|
|
|
|
|
|
self.headX1 = self.armX - self.headWidth |
|
|
|
self.headX2 = self.armX + self.headWidth |
|
|
|
self.headY1 = (self.width/2.0) - self.headWidth |
|
|
|
self.headY2 = (self.width/2.0) + self.headWidth |
|
|
|
|
|
|
|
if self.graphics: |
|
|
|
self.armID = self.canvas.create_rectangle(self.armX1, self.armY1, self.armX2, self.armY2, fill='gray', outline='black') |
|
|
|
self.headID = self.canvas.create_rectangle(self.headX1, self.headY1, self.headX2, self.headY2, fill='gray', outline='black') |
|
|
|
|
|
|
|
self.targetSize = 10.0 |
|
|
|
if self.graphics: |
|
|
|
sz = self.targetSize |
|
|
|
self.targetID = self.canvas.create_oval(self.armX1-sz, self.armY1-sz, self.armX1+sz, self.armY1+sz, fill='orange', outline='') |
|
|
|
|
|
|
|
# IO QUEUE |
|
|
|
self.queueX = 20 |
|
|
|
self.queueY = 450 |
|
|
|
|
|
|
|
self.requestCount = 0 |
|
|
|
self.requestQueue = [] |
|
|
|
self.requestState = [] |
|
|
|
self.queueBoxSize = 20 |
|
|
|
self.queueBoxID = {} |
|
|
|
self.queueTxtID = {} |
|
|
|
|
|
|
|
# draw each box |
|
|
|
for index in range(len(self.requests)): |
|
|
|
self.AddQueueEntry(int(self.requests[index]), index) |
|
|
|
if self.graphics: |
|
|
|
self.canvas.create_text(self.queueX - 5, self.queueY - 20, anchor='w', text='Queue:') |
|
|
|
|
|
|
|
# scheduling window |
|
|
|
self.currWindow = self.window |
|
|
|
|
|
|
|
# draw current limits of queue |
|
|
|
if self.graphics: |
|
|
|
self.windowID = -1 |
|
|
|
self.DrawWindow() |
|
|
|
|
|
|
|
# initial scheduling info |
|
|
|
self.currentIndex = -1 |
|
|
|
self.currentBlock = -1 |
|
|
|
|
|
|
|
# initial state of disk (vs seeking, rotating, transferring) |
|
|
|
self.state = STATE_NULL |
|
|
|
|
|
|
|
# DRAW BLOCKS on the TRACKS |
|
|
|
for bid in range(len(self.blockInfoList)): |
|
|
|
(track, angle, name) = self.blockInfoList[bid] |
|
|
|
if self.graphics: |
|
|
|
distFromSpindle = self.tracks[track] |
|
|
|
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(angle))) |
|
|
|
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(angle))) |
|
|
|
cid = self.canvas.create_text(xc, yc, text=name, anchor='center') |
|
|
|
else: |
|
|
|
cid = -1 |
|
|
|
self.blockInfoList[bid] = (track, angle, name, cid) |
|
|
|
|
|
|
|
# angle of rotation |
|
|
|
self.angle = Decimal(0.0) |
|
|
|
|
|
|
|
# TIME INFO |
|
|
|
if self.graphics: |
|
|
|
self.timeID = self.canvas.create_text(10, 10, text='Time: 0.00', anchor='w') |
|
|
|
self.canvas.create_rectangle(95,0,200,18, fill='orange', outline='orange') |
|
|
|
self.seekID = self.canvas.create_text(100, 10, text='Seek: 0.00', anchor='w') |
|
|
|
self.canvas.create_rectangle(195,0,300,18, fill='lightblue', outline='lightblue') |
|
|
|
self.rotID = self.canvas.create_text(200, 10, text='Rotate: 0.00', anchor='w') |
|
|
|
self.canvas.create_rectangle(295,0,400,18, fill='green', outline='green') |
|
|
|
self.xferID = self.canvas.create_text(300, 10, text='Transfer: 0.00', anchor='w') |
|
|
|
self.canvas.create_text(320, 40, text='"s" to start', anchor='w') |
|
|
|
self.canvas.create_text(320, 60, text='"p" to pause', anchor='w') |
|
|
|
self.canvas.create_text(320, 80, text='"q" to quit', anchor='w') |
|
|
|
self.timer = 0 |
|
|
|
|
|
|
|
# STATS |
|
|
|
self.seekTotal = 0.0 |
|
|
|
self.rotTotal = 0.0 |
|
|
|
self.xferTotal = 0.0 |
|
|
|
|
|
|
|
# set up animation loop |
|
|
|
if self.graphics: |
|
|
|
self.doAnimate = True |
|
|
|
else: |
|
|
|
self.doAnimate = False |
|
|
|
self.isDone = False |
|
|
|
|
|
|
|
# call this to start simulation |
|
|
|
def Go(self): |
|
|
|
if options.graphics: |
|
|
|
self.root.mainloop() |
|
|
|
else: |
|
|
|
self.GetNextIO() |
|
|
|
while self.isDone == False: |
|
|
|
self.Animate() |
|
|
|
|
|
|
|
# crappy error message |
|
|
|
def PrintAddrDescMessage(self, value): |
|
|
|
print 'Bad address description (%s)' % value |
|
|
|
print 'The address description must be a comma-separated list of length three, without spaces.' |
|
|
|
print 'For example, "10,100,0" would indicate that 10 addresses should be generated, with' |
|
|
|
print '100 as the maximum value, and 0 as the minumum. A max of -1 means just use the highest' |
|
|
|
print 'possible value as the max address to generate.' |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
# |
|
|
|
# ZONES AND BLOCK LAYOUT |
|
|
|
# |
|
|
|
def InitBlockLayout(self): |
|
|
|
self.blockInfoList = [] |
|
|
|
self.blockToTrackMap = {} |
|
|
|
self.blockToAngleMap = {} |
|
|
|
self.tracksBeginEnd = {} |
|
|
|
self.blockAngleOffset = [] |
|
|
|
|
|
|
|
zones = self.zoning.split(',') |
|
|
|
assert(len(zones) == 3) |
|
|
|
for i in range(len(zones)): |
|
|
|
self.blockAngleOffset.append(int(zones[i]) / 2) |
|
|
|
|
|
|
|
track = 0 # outer track |
|
|
|
angleOffset = 2 * self.blockAngleOffset[track] |
|
|
|
for angle in range(0, 360, angleOffset): |
|
|
|
block = angle / angleOffset |
|
|
|
self.blockToTrackMap[block] = track |
|
|
|
self.blockToAngleMap[block] = angle |
|
|
|
self.blockInfoList.append((track, angle, block)) |
|
|
|
self.tracksBeginEnd[track] = (0, block) |
|
|
|
pblock = block + 1 |
|
|
|
|
|
|
|
track = 1 # middle track |
|
|
|
skew = self.skew |
|
|
|
angleOffset = 2 * self.blockAngleOffset[track] |
|
|
|
for angle in range(0, 360, angleOffset): |
|
|
|
block = (angle / angleOffset) + pblock |
|
|
|
self.blockToTrackMap[block] = track |
|
|
|
self.blockToAngleMap[block] = angle + (angleOffset * skew) |
|
|
|
self.blockInfoList.append((track, angle + (angleOffset * skew), block)) |
|
|
|
self.tracksBeginEnd[track] = (pblock, block) |
|
|
|
pblock = block + 1 |
|
|
|
|
|
|
|
track = 2 # inner track |
|
|
|
skew = 2 * self.skew |
|
|
|
angleOffset = 2 * self.blockAngleOffset[track] |
|
|
|
for angle in range(0, 360, angleOffset): |
|
|
|
block = (angle / angleOffset) + pblock |
|
|
|
self.blockToTrackMap[block] = track |
|
|
|
self.blockToAngleMap[block] = angle + (angleOffset * skew) |
|
|
|
self.blockInfoList.append((track, angle + (angleOffset * skew), block)) |
|
|
|
self.tracksBeginEnd[track] = (pblock, block) |
|
|
|
self.maxBlock = pblock |
|
|
|
# print 'MAX BLOCK:', self.maxBlock |
|
|
|
|
|
|
|
# adjust angle to starting position relative |
|
|
|
for i in self.blockToAngleMap: |
|
|
|
self.blockToAngleMap[i] = (self.blockToAngleMap[i] + 180) % 360 |
|
|
|
|
|
|
|
# print 'btoa map', self.blockToAngleMap |
|
|
|
# print 'btot map', self.blockToTrackMap |
|
|
|
# print 'bao', self.blockAngleOffset |
|
|
|
|
|
|
|
def MakeRequests(self, addr, addrDesc): |
|
|
|
(numRequests, maxRequest, minRequest) = (0, 0, 0) |
|
|
|
if addr == '-1': |
|
|
|
# first extract values from descriptor |
|
|
|
desc = addrDesc.split(',') |
|
|
|
if len(desc) != 3: |
|
|
|
self.PrintAddrDescMessage(addrDesc) |
|
|
|
(numRequests, maxRequest, minRequest) = (int(desc[0]), int(desc[1]), int(desc[2])) |
|
|
|
if maxRequest == -1: |
|
|
|
maxRequest = self.maxBlock |
|
|
|
# now make list |
|
|
|
tmpList = [] |
|
|
|
for i in range(numRequests): |
|
|
|
tmpList.append(int(random.random() * maxRequest) + minRequest) |
|
|
|
return tmpList |
|
|
|
else: |
|
|
|
return addr.split(',') |
|
|
|
|
|
|
|
# |
|
|
|
# BUTTONS |
|
|
|
# |
|
|
|
def Start(self, event): |
|
|
|
self.GetNextIO() |
|
|
|
self.doAnimate = True |
|
|
|
self.Animate() |
|
|
|
|
|
|
|
def Pause(self, event): |
|
|
|
if self.doAnimate == False: |
|
|
|
self.doAnimate = True |
|
|
|
else: |
|
|
|
self.doAnimate = False |
|
|
|
|
|
|
|
def Exit(self, event): |
|
|
|
sys.exit(0) |
|
|
|
|
|
|
|
# |
|
|
|
# CORE SIMULATION and ANIMATION |
|
|
|
# |
|
|
|
def UpdateTime(self): |
|
|
|
if self.graphics: |
|
|
|
self.canvas.itemconfig(self.timeID, text='Time: ' + str(self.timer)) |
|
|
|
self.canvas.itemconfig(self.seekID, text='Seek: ' + str(self.seekTotal)) |
|
|
|
self.canvas.itemconfig(self.rotID, text='Rotate: ' + str(self.rotTotal)) |
|
|
|
self.canvas.itemconfig(self.xferID, text='Transfer: ' + str(self.xferTotal)) |
|
|
|
|
|
|
|
def AddRequest(self, block): |
|
|
|
self.AddQueueEntry(block, len(self.requestQueue)) |
|
|
|
|
|
|
|
def QueueMap(self, index): |
|
|
|
numPerRow = 400 / self.queueBoxSize |
|
|
|
return (index % numPerRow, index / numPerRow) |
|
|
|
|
|
|
|
def DrawWindow(self): |
|
|
|
if self.window == -1: |
|
|
|
return |
|
|
|
(col, row) = self.QueueMap(self.currWindow) |
|
|
|
if col == 0: |
|
|
|
(col, row) = (20, row - 1) |
|
|
|
if self.windowID != -1: |
|
|
|
self.canvas.delete(self.windowID) |
|
|
|
self.windowID = self.canvas.create_line(self.queueX + (col * 20) - 10, self.queueY - 13 + (row * 20), |
|
|
|
self.queueX + (col * 20) - 10, self.queueY + 13 + (row * 20), width=2) |
|
|
|
|
|
|
|
def AddQueueEntry(self, block, index): |
|
|
|
self.requestQueue.append((block, index)) |
|
|
|
self.requestState.append(STATE_NULL) |
|
|
|
if self.graphics: |
|
|
|
(col, row) = self.QueueMap(index) |
|
|
|
sizeHalf = self.queueBoxSize / 2.0 |
|
|
|
(cx, cy) = (self.queueX + (col * self.queueBoxSize), self.queueY + (row * self.queueBoxSize)) |
|
|
|
self.queueBoxID[index] = self.canvas.create_rectangle(cx - sizeHalf, cy - sizeHalf, cx + sizeHalf, cy + sizeHalf, fill='white') |
|
|
|
self.queueTxtID[index] = self.canvas.create_text(cx, cy, anchor='center', text=str(block)) |
|
|
|
|
|
|
|
def SwitchColors(self, c): |
|
|
|
if self.graphics: |
|
|
|
self.canvas.itemconfig(self.queueBoxID[self.currentIndex], fill=c) |
|
|
|
self.canvas.itemconfig(self.targetID, fill=c) |
|
|
|
|
|
|
|
def SwitchState(self, newState): |
|
|
|
self.state = newState |
|
|
|
self.requestState[self.currentIndex] = newState |
|
|
|
|
|
|
|
def RadiallyCloseTo(self, a1, a2): |
|
|
|
if a1 > a2: |
|
|
|
v = a1 - a2 |
|
|
|
else: |
|
|
|
v = a2 - a1 |
|
|
|
if v < self.rotateSpeed: |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def DoneWithTransfer(self): |
|
|
|
angleOffset = self.blockAngleOffset[self.armTrack] |
|
|
|
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] + angleOffset) % 360: |
|
|
|
if self.RadiallyCloseTo(self.angle, Decimal((self.blockToAngleMap[self.currentBlock] + angleOffset) % 360)): |
|
|
|
# print 'END TRANSFER', self.angle, self.timer |
|
|
|
self.SwitchState(STATE_DONE) |
|
|
|
self.requestCount += 1 |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def DoneWithRotation(self): |
|
|
|
angleOffset = self.blockAngleOffset[self.armTrack] |
|
|
|
# XXX there is a weird bug in here |
|
|
|
# print self.timer, 'ROTATE:: ', self.currentBlock, 'currangle: ', self.angle, ' - mapangle: ', self.blockToAngleMap[self.currentBlock] |
|
|
|
# print ' angleOffset ', angleOffset |
|
|
|
# print ' blockMap ', (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360 |
|
|
|
# print ' self.angle ', self.angle, int(self.angle) |
|
|
|
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360: |
|
|
|
if self.RadiallyCloseTo(self.angle, Decimal((self.blockToAngleMap[self.currentBlock] - angleOffset) % 360)): |
|
|
|
self.SwitchState(STATE_XFER) |
|
|
|
# print ' --> DONE WITH ROTATION!', self.timer |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def PlanSeek(self, track): |
|
|
|
self.seekBegin = self.timer |
|
|
|
self.SwitchColors('orange') |
|
|
|
self.SwitchState(STATE_SEEK) |
|
|
|
if track == self.armTrack: |
|
|
|
self.rotBegin = self.timer |
|
|
|
self.SwitchColors('lightblue') |
|
|
|
self.SwitchState(STATE_ROTATE) |
|
|
|
return |
|
|
|
self.armTarget = track |
|
|
|
self.armTargetX1 = self.spindleX - self.tracks[track] - (self.trackWidth / 2.0) |
|
|
|
if track >= self.armTrack: |
|
|
|
self.armSpeed = self.armSpeedBase |
|
|
|
else: |
|
|
|
self.armSpeed = - self.armSpeedBase |
|
|
|
|
|
|
|
def DoneWithSeek(self): |
|
|
|
# move the disk arm |
|
|
|
self.armX1 += self.armSpeed |
|
|
|
self.armX2 += self.armSpeed |
|
|
|
self.headX1 += self.armSpeed |
|
|
|
self.headX2 += self.armSpeed |
|
|
|
# update it on screen |
|
|
|
if self.graphics: |
|
|
|
self.canvas.coords(self.armID, self.armX1, self.armY1, self.armX2, self.armY2) |
|
|
|
self.canvas.coords(self.headID, self.headX1, self.headY1, self.headX2, self.headY2) |
|
|
|
# check if done |
|
|
|
if (self.armSpeed > 0.0 and self.armX1 >= self.armTargetX1) or (self.armSpeed < 0.0 and self.armX1 <= self.armTargetX1): |
|
|
|
self.armTrack = self.armTarget |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def DoSATF(self, rList): |
|
|
|
minBlock = -1 |
|
|
|
minIndex = -1 |
|
|
|
minEst = -1 |
|
|
|
|
|
|
|
# print '**** DoSATF ****', rList |
|
|
|
for (block, index) in rList: |
|
|
|
if self.requestState[index] == STATE_DONE: |
|
|
|
continue |
|
|
|
track = self.blockToTrackMap[block] |
|
|
|
angle = self.blockToAngleMap[block] |
|
|
|
# print 'track', track, 'angle', angle |
|
|
|
|
|
|
|
# estimate seek time |
|
|
|
dist = int(math.fabs(self.armTrack - track)) |
|
|
|
seekEst = Decimal(self.trackWidth / self.armSpeedBase) * dist |
|
|
|
|
|
|
|
# estimate rotate time |
|
|
|
angleOffset = self.blockAngleOffset[track] |
|
|
|
angleAtArrival = (Decimal(self.angle) + (seekEst * self.rotateSpeed)) |
|
|
|
while angleAtArrival > 360.0: |
|
|
|
angleAtArrival -= 360.0 |
|
|
|
rotDist = Decimal((angle - angleOffset) - angleAtArrival) |
|
|
|
while rotDist > 360.0: |
|
|
|
rotDist -= Decimal(360.0) |
|
|
|
while rotDist < 0.0: |
|
|
|
rotDist += Decimal(360.0) |
|
|
|
rotEst = rotDist / self.rotateSpeed |
|
|
|
|
|
|
|
# finally, transfer |
|
|
|
xferEst = (Decimal(angleOffset) * Decimal(2.0)) / self.rotateSpeed |
|
|
|
|
|
|
|
totalEst = seekEst + rotEst + xferEst |
|
|
|
|
|
|
|
# should probably pick one on same track in case of a TIE |
|
|
|
if minEst == -1 or totalEst < minEst: |
|
|
|
minEst = totalEst |
|
|
|
minBlock = block |
|
|
|
minIndex = index |
|
|
|
# END loop |
|
|
|
|
|
|
|
# when done |
|
|
|
self.totalEst = minEst |
|
|
|
assert(minBlock != -1) |
|
|
|
assert(minIndex != -1) |
|
|
|
return (minBlock, minIndex) |
|
|
|
|
|
|
|
# |
|
|
|
# actually doesn't quite do SSTF |
|
|
|
# just finds all the blocks on the nearest track |
|
|
|
# (whatever that may be) and returns it as a list |
|
|
|
# |
|
|
|
def DoSSTF(self, rList): |
|
|
|
minDist = MAXTRACKS |
|
|
|
minBlock = -1 |
|
|
|
trackList = [] # all the blocks on a track |
|
|
|
|
|
|
|
for (block, index) in rList: |
|
|
|
if self.requestState[index] == STATE_DONE: |
|
|
|
continue |
|
|
|
track = self.blockToTrackMap[block] |
|
|
|
dist = int(math.fabs(self.armTrack - track)) |
|
|
|
if dist < minDist: |
|
|
|
trackList = [] |
|
|
|
trackList.append((block, index)) |
|
|
|
minDist = dist |
|
|
|
elif dist == minDist: |
|
|
|
trackList.append((block, index)) |
|
|
|
assert(trackList != []) |
|
|
|
return trackList |
|
|
|
|
|
|
|
def UpdateWindow(self): |
|
|
|
if self.fairWindow == -1 and self.currWindow > 0 and self.currWindow < len(self.requestQueue): |
|
|
|
self.currWindow += 1 |
|
|
|
if self.graphics: |
|
|
|
self.DrawWindow() |
|
|
|
|
|
|
|
def GetWindow(self): |
|
|
|
if self.currWindow <= -1: |
|
|
|
return len(self.requestQueue) |
|
|
|
else: |
|
|
|
if self.fairWindow != -1: |
|
|
|
if self.requestCount > 0 and (self.requestCount % self.fairWindow == 0): |
|
|
|
self.currWindow = self.currWindow + self.fairWindow |
|
|
|
if self.currWindow > len(self.requestQueue): |
|
|
|
self.currWindow = len(self.requestQueue) |
|
|
|
if self.graphics: |
|
|
|
self.DrawWindow() |
|
|
|
return self.currWindow |
|
|
|
else: |
|
|
|
return self.currWindow |
|
|
|
|
|
|
|
def GetNextIO(self): |
|
|
|
# check if done: if so, print stats and end animation |
|
|
|
if self.requestCount == len(self.requestQueue): |
|
|
|
self.UpdateTime() |
|
|
|
self.PrintStats() |
|
|
|
self.doAnimate = False |
|
|
|
self.isDone = True |
|
|
|
return |
|
|
|
|
|
|
|
# do policy: should set currentBlock, |
|
|
|
if self.policy == 'FIFO': |
|
|
|
(self.currentBlock, self.currentIndex) = self.requestQueue[self.requestCount] |
|
|
|
self.DoSATF(self.requestQueue[self.requestCount:self.requestCount+1]) |
|
|
|
elif self.policy == 'SATF' or self.policy == 'BSATF': |
|
|
|
(self.currentBlock, self.currentIndex) = self.DoSATF(self.requestQueue[0:self.GetWindow()]) |
|
|
|
elif self.policy == 'SSTF': |
|
|
|
# first, find all the blocks on a given track (given window constraints) |
|
|
|
trackList = self.DoSSTF(self.requestQueue[0:self.GetWindow()]) |
|
|
|
# then, do SATF on those blocks (otherwise, will not do them in obvious order) |
|
|
|
(self.currentBlock, self.currentIndex) = self.DoSATF(trackList) |
|
|
|
else: |
|
|
|
print 'policy (%s) not implemented' % self.policy |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
# once best block is decided, go ahead and do the seek |
|
|
|
self.PlanSeek(self.blockToTrackMap[self.currentBlock]) |
|
|
|
|
|
|
|
# add another block? |
|
|
|
if len(self.lateRequests) > 0 and self.lateCount < len(self.lateRequests): |
|
|
|
self.AddRequest(self.lateRequests[self.lateCount]) |
|
|
|
self.lateCount += 1 |
|
|
|
|
|
|
|
def Animate(self): |
|
|
|
if self.graphics == True and self.doAnimate == False: |
|
|
|
self.root.after(20, self.Animate) |
|
|
|
return |
|
|
|
|
|
|
|
# timer |
|
|
|
self.timer += 1 |
|
|
|
self.UpdateTime() |
|
|
|
|
|
|
|
# see which blocks are rotating on the disk |
|
|
|
# print 'SELF ANGLE', self.angle |
|
|
|
self.angle = Decimal(self.angle + self.rotateSpeed) |
|
|
|
if self.angle >= 360.0: |
|
|
|
self.angle = Decimal(0.0) |
|
|
|
|
|
|
|
# move the blocks |
|
|
|
if self.graphics: |
|
|
|
for (track, angle, name, cid) in self.blockInfoList: |
|
|
|
distFromSpindle = self.tracks[track] |
|
|
|
na = angle - self.angle |
|
|
|
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(na))) |
|
|
|
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(na))) |
|
|
|
if self.graphics: |
|
|
|
self.canvas.coords(cid, xc, yc) |
|
|
|
if self.currentBlock == name: |
|
|
|
sz = self.targetSize |
|
|
|
self.canvas.coords(self.targetID, xc-sz, yc-sz, xc+sz, yc+sz) |
|
|
|
|
|
|
|
# move the arm OR wait for a rotational delay |
|
|
|
if self.state == STATE_SEEK: |
|
|
|
if self.DoneWithSeek(): |
|
|
|
self.rotBegin = self.timer |
|
|
|
self.SwitchState(STATE_ROTATE) |
|
|
|
self.SwitchColors('lightblue') |
|
|
|
if self.state == STATE_ROTATE: |
|
|
|
# check for read (disk arm must be settled) |
|
|
|
if self.DoneWithRotation(): |
|
|
|
self.xferBegin = self.timer |
|
|
|
self.SwitchState(STATE_XFER) |
|
|
|
self.SwitchColors('green') |
|
|
|
if self.state == STATE_XFER: |
|
|
|
if self.DoneWithTransfer(): |
|
|
|
self.DoRequestStats() |
|
|
|
self.SwitchState(STATE_DONE) |
|
|
|
self.SwitchColors('red') |
|
|
|
self.UpdateWindow() |
|
|
|
currentBlock = self.currentBlock |
|
|
|
self.GetNextIO() |
|
|
|
nextBlock = self.currentBlock |
|
|
|
if self.blockToTrackMap[currentBlock] == self.blockToTrackMap[nextBlock]: |
|
|
|
if (currentBlock == self.tracksBeginEnd[self.armTrack][1] and nextBlock == self.tracksBeginEnd[self.armTrack][0]) or (currentBlock + 1 == nextBlock): |
|
|
|
# need a special case here: to handle when we stay in transfer mode |
|
|
|
(self.rotBegin, self.seekBegin, self.xferBegin) = (self.timer, self.timer, self.timer) |
|
|
|
self.SwitchState(STATE_XFER) |
|
|
|
self.SwitchColors('green') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# make sure to keep the animation going! |
|
|
|
if self.graphics: |
|
|
|
self.root.after(20, self.Animate) |
|
|
|
|
|
|
|
def DoRequestStats(self): |
|
|
|
seekTime = self.rotBegin - self.seekBegin |
|
|
|
rotTime = self.xferBegin - self.rotBegin |
|
|
|
xferTime = self.timer - self.xferBegin |
|
|
|
totalTime = self.timer - self.seekBegin |
|
|
|
|
|
|
|
if self.compute == True: |
|
|
|
print 'Block: %3d Seek:%3d Rotate:%3d Transfer:%3d Total:%4d' % (self.currentBlock, seekTime, rotTime, xferTime, totalTime) |
|
|
|
|
|
|
|
# if int(totalTime) != int(self.totalEst): |
|
|
|
# print 'INTERNAL ERROR: estimate was', self.totalEst, 'whereas actual time to access block was', totalTime |
|
|
|
# print 'Please report this bug and as much information as possible so as to make it easy to recreate. Thanks!' |
|
|
|
|
|
|
|
# update stats |
|
|
|
self.seekTotal += seekTime |
|
|
|
self.rotTotal += rotTime |
|
|
|
self.xferTotal += xferTime |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def PrintStats(self): |
|
|
|
if self.compute == True: |
|
|
|
print '\nTOTALS Seek:%3d Rotate:%3d Transfer:%3d Total:%4d\n' % (self.seekTotal, self.rotTotal, self.xferTotal, self.timer) |
|
|
|
|
|
|
|
# END: class Disk |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# |
|
|
|
# MAIN SIMULATOR |
|
|
|
# |
|
|
|
parser = OptionParser() |
|
|
|
parser.add_option('-s', '--seed', default='0', help='Random seed', action='store', type='int', dest='seed') |
|
|
|
parser.add_option('-a', '--addr', default='-1', help='Request list (comma-separated) [-1 -> use addrDesc]', action='store', type='string', dest='addr') |
|
|
|
parser.add_option('-A', '--addrDesc', default='5,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='addrDesc') |
|
|
|
parser.add_option('-S', '--seekSpeed', default='1', help='Speed of seek', action='store', type='string', dest='seekSpeed') |
|
|
|
parser.add_option('-R', '--rotSpeed', default='1', help='Speed of rotation', action='store', type='string', dest='rotateSpeed') |
|
|
|
parser.add_option('-p', '--policy', default='FIFO', help='Scheduling policy (FIFO, SSTF, SATF, BSATF)', action='store', type='string', dest='policy') |
|
|
|
parser.add_option('-w', '--schedWindow', default=-1, help='Size of scheduling window (-1 -> all)', action='store', type='int', dest='window') |
|
|
|
parser.add_option('-o', '--skewOffset', default=0, help='Amount of skew (in blocks)', action='store', type='int', dest='skew') |
|
|
|
parser.add_option('-z', '--zoning', default='30,30,30', help='Angles between blocks on outer,middle,inner tracks', action='store', type='string', dest='zoning') |
|
|
|
parser.add_option('-G', '--graphics', default=False, help='Turn on graphics', action='store_true', dest='graphics') |
|
|
|
parser.add_option('-l', '--lateAddr', default='-1', help='Late: request list (comma-separated) [-1 -> random]', action='store', type='string', dest='lateAddr') |
|
|
|
parser.add_option('-L', '--lateAddrDesc', default='0,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='lateAddrDesc') |
|
|
|
parser.add_option('-c', '--compute', default=False, help='Compute the answers', action='store_true', dest='compute') |
|
|
|
(options, args) = parser.parse_args() |
|
|
|
|
|
|
|
print 'OPTIONS seed', options.seed |
|
|
|
print 'OPTIONS addr', options.addr |
|
|
|
print 'OPTIONS addrDesc', options.addrDesc |
|
|
|
print 'OPTIONS seekSpeed', options.seekSpeed |
|
|
|
print 'OPTIONS rotateSpeed', options.rotateSpeed |
|
|
|
print 'OPTIONS skew', options.skew |
|
|
|
print 'OPTIONS window', options.window |
|
|
|
print 'OPTIONS policy', options.policy |
|
|
|
print 'OPTIONS compute', options.compute |
|
|
|
print 'OPTIONS graphics', options.graphics |
|
|
|
print 'OPTIONS zoning', options.zoning |
|
|
|
print 'OPTIONS lateAddr', options.lateAddr |
|
|
|
print 'OPTIONS lateAddrDesc', options.lateAddrDesc |
|
|
|
print '' |
|
|
|
|
|
|
|
if options.window == 0: |
|
|
|
print 'Scheduling window (%d) must be positive or -1 (which means a full window)' % options.window |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
if options.graphics and options.compute == False: |
|
|
|
print '\nWARNING: Setting compute flag to True, as graphics are on\n' |
|
|
|
options.compute = True |
|
|
|
|
|
|
|
# set up simulator info |
|
|
|
d = Disk(addr=options.addr, addrDesc=options.addrDesc, lateAddr=options.lateAddr, lateAddrDesc=options.lateAddrDesc, |
|
|
|
policy=options.policy, seekSpeed=Decimal(options.seekSpeed), rotateSpeed=Decimal(options.rotateSpeed), |
|
|
|
skew=options.skew, window=options.window, compute=options.compute, graphics=options.graphics, zoning=options.zoning) |
|
|
|
|
|
|
|
# run simulation |
|
|
|
d.Go() |