initial disk sim push

This commit is contained in:
Remzi Arpaci-Dusseau 2020-06-08 16:14:41 -05:00
parent 7147dc7458
commit 3b683607ba
3 changed files with 1635 additions and 0 deletions

145
file-disks/README.md Normal file
View File

@ -0,0 +1,145 @@
# Overview
This homework uses `disk.py` to familiarize you with how a modern hard
drive works. It has a lot of different options, and unlike most of the other
simulations, has a graphical animator to show you exactly what happens when
the disk is in action.
Note: there is also an experimental program, `disk-precise.py`. This
version of the simulator uses the python Decimal package for precise
floating point computation, thus giving slightly better answers in
some corner cases than `disk.py`. However, it has not been very
carefully tested, so use at your own caution.
Let's do a simple example first. To run the simulator and compute some basic
seek, rotation, and transfer times, you first have to give a list of requests
to the simulator. This can either be done by specifying the exact requests, or
by having the simulator generate some randomly.
We'll start by specifying a list of requests ourselves. Let's do a single
request first:
```sh
prompt> disk.py -a 10
```
At this point you'll see:
```sh
...
REQUESTS [br '10']
For the requests above, compute the seek, rotate, and transfer times.
Use -c or the graphical mode (-G) to see the answers.
```
To be able to compute the seek, rotation, and transfer times for this request,
you'll have to know a little more information about the layout of sectors, the
starting position of the disk head, and so forth. To see much of this
information, run the simulator in graphical mode (-G):
```sh
prompt> ./disk.py -a 10 -G
```
At this point, a window should appear with our simple disk on it.
The disk head is positioned on the outside track, halfway through sector 6.
As you can see, sector 10 (our example sector) is on the same track, about a
third of the way around. The direction of rotation is counter-clockwise.
To run the simulation, press the "s" key while the simulator window is
highlighted.
When the simulation completes, you should be able to see that the disk spent
105 time units in rotation and 30 in transfer in order to access sector 10,
with no seek time. Press "q" to close the simulator window.
To calculate this (instead of just running the simulation), you would need to
know a few details about the disk. First, the rotational speed is by default
set to 1 degree per time unit. Thus, to make a complete revolution, it takes
360 time units. Second, transfer begins and ends at the halfway point between
sectors. Thus, to read sector 10, the transfer begins halfway between 9 and 10,
and ends halfway between 10 and 11. Finally, in the default disk, there are
12 sectors per track, meaning that each sector takes up 30 degrees of the
rotational space. Thus, to read a sector, it takes 30 time units (given our
default speed of rotation).
With this information in hand, you now should be able to compute the seek,
rotation, and transfer times for accessing sector 10. Because the head starts
on the same track as 10, there is no seek time. Because the disk rotates at
1 degree / time unit, it takes 105 time units to get to the beginning of sector
10, halfway between 9 and 10 (note that it is exactly 90 degrees to the middle
of sector 9, and another 15 to the halfway point). Finally, to transfer the
sector takes 30 time units.
Now let's do a slightly more complex example:
```sh
prompt> ./disk.py -a 10,11 -G
```
In this case, we're transferring two sectors, 10 and 11. How long will it take?
Try guessing before running the simulation!
As you probably guessed, this simulation takes just 30 time units longer, to
transfer the next sector 11. Thus, the seek and rotate times remain the same,
but the transfer time for the requests is doubled. You can in fact see these
sums across the top of the simulator window; they also get printed out to the
console as follows:
```sh
...
Sector: 10 Seek: 0 Rotate:105 Transfer: 30 Total: 135
Sector: 11 Seek: 0 Rotate: 0 Transfer: 30 Total: 30
TOTALS Seek: 0 Rotate:105 Transfer: 60 Total: 165
```
Now let's do an example with a seek. Try the following set of requests:
```sh
prompt> disk.py -a 10,18 -G
```
To compute how long this will take, you need to know how long a seek will
take. The distance between each track is by default 40 distance units, and the
default rate of seeking is 1 distance unit per unit time. Thus, a seek from
the outer track to the middle track takes 40 time units.
You'd also have to know the scheduling policy. The default is FIFO, though, so
for now you can just compute the request times assuming the processing order
matches the list specified via the `-a` flag.
To compute how long it will take the disk to service these requests, we first
compute how long it takes to access sector 10, which we know from above to be
135 time units (105 rotating, 30 transferring). Once this request is complete,
the disk begins to seek to the middle track where sector 18 lies, taking 40
time units. Then the disk rotates to sector 18, and transfers it for 30 time
units, thus completing the simulation. But how long does this final rotation
take?
To compute the rotational delay for 18, first figure out how long the disk
would take to rotate from the end of the access to sector 10 to the beginning
of the access to sector 18, assuming a zero-cost seek. As you can see from the
simulator, sector 10 on the outer track is lined up with sector 22 on the middle
track, and there are 7 sectors separating 22 from 18 (23, 12, 13, 14, 15, 16,
and 17, as the disk spins counter-clockwise). Rotating through 7 sectors takes
210 time units (30 per sector). However, the first part of this rotation is
actually spent seeking to the middle track, for 40 time units. Thus, the
actual rotational delay for accessing sector 18 is 210 minus 40, or 170 time
units. Run the simulator to see this for yourself; note that you can run
without graphics and with the "-c" flag to just see the results without
seeing the graphics.
```sh
prompt> ./disk.py -a 10,18 -c
...
Sector: 10 Seek: 0 Rotate:105 Transfer: 30 Total: 135
Sector: 18 Seek: 40 Rotate:170 Transfer: 30 Total: 240
TOTALS Seek: 40 Rotate:275 Transfer: 60 Total: 375
```
You should now have a basic idea of how the simulator works. The questions
below will explore some of the different options, to better help you build a
model of how a disk really works.

733
file-disks/disk-precise.py Executable file
View File

@ -0,0 +1,733 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import division
try:
from Tkinter import *
except:
from tkinter import *
from types import *
import math, random, time, sys, os
from optparse import OptionParser
from decimal import *
# to make Python2 and Python3 act the same -- how dumb
def random_seed(seed):
try:
random.seed(seed, version=1)
except:
random.seed(seed)
return
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.0) * 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 (%f) must divide evenly into track width (%f)' % (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 (%f) must divide evenly into track width (%f)' % (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()

757
file-disks/disk.py Executable file
View File

@ -0,0 +1,757 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import division
try:
from Tkinter import *
except:
from tkinter import *
from types import *
import math, random, time, sys, os
from optparse import OptionParser
# to make Python2 and Python3 act the same -- how dumb
def random_seed(seed):
try:
random.seed(seed, version=1)
except:
random.seed(seed)
return
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 = seekSpeed
self.rotateSpeed = 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.0) * 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 (%f) 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 = 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)):
print('z', i, zones[i])
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
print(track, angleOffset, block)
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
print(track, skew, angleOffset, block)
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
print(track, skew, angleOffset, block)
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, float((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, float((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:
# print ' Skip', index
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 = (self.trackWidth / self.armSpeedBase) * dist
# print(' dist', dist)
# print(' seekEst', seekEst)
# estimate rotate time
angleOffset = self.blockAngleOffset[track]
# print ' angleOffset', angleOffset
# print ' self.angle', self.angle
angleAtArrival = (self.angle + (seekEst * self.rotateSpeed))
while angleAtArrival > 360.0:
angleAtArrival -= 360.0
# print 'self.rotateSpeed', self.rotateSpeed
# print 'angleAtArrival', angleAtArrival
rotDist = ((angle - angleOffset) - angleAtArrival)
while rotDist > 360.0:
rotDist -= 360.0
while rotDist < 0.0:
rotDist += 360.0
rotEst = rotDist / self.rotateSpeed
# print ' rotEst', rotDist, self.rotateSpeed, ' -> ', rotEst
# finally, transfer
xferEst = (angleOffset * 2.0) / self.rotateSpeed
# print ' xferEst', xferEst
totalEst = seekEst + rotEst + xferEst
# print ' totalEst', seekEst, rotEst, xferEst, ' -> ', totalEst
# print ' --> block:%d seek:%d rotate:%d xfer:%d est:%d' % (block, seekEst, rotEst, xferEst, totalEst)
# should probably pick one on same track in case of a TIE
if minEst == -1 or totalEst < minEst:
minEst = totalEst
minBlock = block
minIndex = index
# print ' UPDATE new minBlock', minBlock, minIndex
# print ''
# 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()
# warning: doesn't just GET the window, but may update it as well
# (when it is time to do so)
def GetWindow(self):
if self.currWindow <= -1:
return len(self.requestQueue)
else:
if self.fairWindow != -1:
# a WINDOW is in place -
# print ' curr window', self.currWindow, ' FAIR window', self.fairWindow, ' request count', self.requestCount
if self.requestCount > 0 and (self.requestCount % self.fairWindow == 0):
self.currWindow = self.currWindow + self.fairWindow
# print ' -> UPDATING current window', self.currWindow
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':
endIndex = self.GetWindow()
# print ' GetWindow():', endIndex
if endIndex > len(self.requestQueue):
endIndex = len(self.requestQueue)
(self.currentBlock, self.currentIndex) = self.DoSATF(self.requestQueue[0:endIndex])
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 = self.angle + self.rotateSpeed
if self.angle >= 360.0:
self.angle = 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=float(options.seekSpeed), rotateSpeed=float(options.rotateSpeed),
skew=options.skew, window=options.window, compute=options.compute, graphics=options.graphics, zoning=options.zoning)
# run simulation
d.Go()