#! /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()
|