Exercise

Enhance your understanding by getting hands-on practice ​with this exercise about hard disk drives!

We'll cover the following

Simulator

This exercise 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 (for you to run locally) to show you exactly what happens when the disk is in action.

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

Questions

  1. Compute the seek, rotation, and transfer times for the following sets of requests: -a 0, -a 6, -a 30, -a 7,30,8, and finally -a 10, 11, 12, 13.
  2. Do the same requests above, but change the seek rate to different values: -S 2, -S 4, -S 8, -S 10, -S 40, -S 0.1. How do the times change?
  3. Do the same requests above, but change the rotation rate: -R 0.1, -R 0.5, -R 0.01. How do the times change?
  4. FIFO is not always best, e.g., with the request stream -a 7, 30, 8, what order should the requests be processed in? Run the shortest seek time first (SSTF) scheduler (-p SSTF) on this workload; how long should it take (seek, rotation, transfer) for each request to be served?
  5. Now use the shortest access-time first (SATF) scheduler (-p SATF). Does it make any difference for -a 7, 30, 8 workload? Find a set of requests where SATF outperforms SSTF; more generally, when is SATF better than SSTF?
  6. Here is a request stream to try: -a 10, 11, 12, 13. What goes poorly when it runs? Try adding track skew to address this problem (-o skew). Given the default seek rate, what should the skew be to maximize performance? What about for different seek rates (e.g., -S 2, -S 4)? In general, could you write a formula to figure out the skew?
  7. Specify a disk with different density per zone, e.g., -z 10, 20, 30, which specifies the angular difference between blocks on the outer, middle, and inner tracks. Run some random requests (e.g., -a -1 -A 5, -1, 0, which specifies that random requests should be used via the -a -1 flag and that five requests ranging from 0 to the max be generated), and compute the seek, rotation, and transfer times. Use different random seeds. What is the bandwidth (in sectors per unit time) on the outer, middle, and inner tracks?
  8. A scheduling window determines how many requests the disk can examine at once. Generate random workloads (e.g., -A 1000, -1, 0, with different seeds) and see how long the SATF scheduler takes when the scheduling window is changed from 1 up to the number of requests. How big of a window is needed to maximize performance? Hint: use the -c flag and don’t turn on graphics (-G) to run these quickly. When the scheduling window is set to 1, does it matter which policy you are using?
  9. Create a series of requests to starve a particular request, assuming an SATF policy. Given that sequence, how does it perform if you use a bounded SATF (BSATF) scheduling approach? In this approach, you specify the scheduling window (e.g., -w 4); the scheduler only moves onto the next window of requests when all requests in the current window have been serviced. Does this solve starvation? How does it perform, as compared to SATF? In general, how should a disk make this trade-off between performance and starvation avoidance?
  10. All the scheduling policies we have looked at thus far are greedy; they pick the next best option instead of looking for an optimal schedule. Can you find a set of requests in which greedy is not optimal?

Get hands-on with 1400+ tech skills courses.