Defrag Part 2
As promised, the second part of the defrag simulator.
Here I added some __str__
overrides to help with debugging but it mostly went very smoothly once I decided on the algorithm and how
it would be run. In the previous post I worked on randomly scattering chunks of files all over the disk and in this part we need
to start bringing them back together.
I kept the FileChunk and File classes but they are not strictly necessary since I went with the approach to just each chunk in turn and try to move it into it's corresponding sector. If there is already a chunk in that spot, I move it to a free space at the end of the disk and then wait for that particular chunk to be moved when we come to it later.
Defrag
Since there is no real timer callbacks available in processing, I elected to run the defrag function on every time through the draw function, picking the next chunk to handle and doing something with it. Next time through the defrag function we just pick up the next chunk and work with that.
sector_width = 30
sector_height = 30
class File:
def __init__(self, id, starting_index):
self.id = id
self.chunks = []
size = int(random(5, 15))
for i in range(size):
chunk = FileChunk(id, starting_index + i)
self.chunks.append(chunk)
def size(self):
return len(self.chunks)
def parts(self):
return self.chunks
def draw(self):
print(self.id) #, self.length())
class FileChunk:
def __init__(self, file_id, index):
self.file_id = file_id
self.index = index
def __str__(self):
return ("file %i chunk %i" %(self.file_id, self.index))
def draw(self):
print(self.file_id, self.index)
SECTOR_FREE = 'free'
SECTOR_ALLOCATED = 'allocated'
SECTOR_TEMPORARY = 'temp'
class DiskSector:
def __init__(self, id):
self.id = id
self.chunk = None
self.status = SECTOR_FREE
def __str__(self):
if self.chunk is not None:
return ("sector %i chunk %i" %(self.id, self.chunk.index))
return ("sector %i" %(self.id))
def allocated(self):
return self.chunk is not None
def unallocated(self):
return self.chunk is None
def allocate(self, part):
print('allocating', str(part), 'to', str(self))
self.chunk = part
print('allocated', str(part), 'to', str(self))
def deallocate(self):
print('deallocating', str(self))
chunk = self.chunk
self.chunk = None
print('deallocated', str(chunk), 'from', str(self))
def current_status(self, text):
self.status = text
def draw(self, x, y):
stroke(255)
if self.chunk is not None:
# assume out of step
fill(255, 0, 0)
# in correct place?
if self.chunk.index == self.id or self.status == SECTOR_ALLOCATED:
fill(0, 255, 0)
else:
if self.status == SECTOR_FREE:
fill(240, 240, 240)
if self.status == SECTOR_TEMPORARY:
fill(129, 151, 237)
square(x, y, sector_width)
textAlign(CENTER, CENTER)
id = ''# str(self.id)
if self.chunk is not None:
id = str(self.chunk.index)
fill(50)
text(id, x + (sector_width / 2), y + (sector_height /2 ))
class FileAllocationTable:
def __init__(self, length):
self.sectors = []
for i in range(length):
self.sectors.append(DiskSector(i))
def unallocated_sectors(self):
return [ i for i, x in enumerate(self.sectors) if x.unallocated()]
def draw(self):
border = 5
x = border
y = border
# layout across and down the screen
for i in range(len(self.sectors)):
sector = self.sectors[i]
sector.draw(x, y)
x += sector_width + 1
# move down the screen?
if x >= (width - sector_width):
x = border
y += sector_height + 1
def scatter(self, files):
for file in files:
for part in file.parts():
index = self.pick_random_index_from(self.unallocated_sectors())
sector = self.sectors[index]
sector.allocate(part)
def pick_random_index_from(self, indices):
randomIndex = int(random(len(indices)))
return indices[randomIndex]
def highest_unallocated_sector_index(self):
return self.unallocated_sectors()[-1]
def move_chunk(self, from_sector, from_status, to_sector, to_status):
print('moving chunk from', from_sector, 'to', to_sector)
fromSector = self.sectors[from_sector]
print('from', str(fromSector))
toSector = self.sectors[to_sector]
print('to', str(toSector))
toSector.allocate(fromSector.chunk)
toSector.current_status(to_status)
fromSector.deallocate()
fromSector.current_status(from_status)
def defrag(self, sector_index, chunk_index):
try:
print('defrag', str(sector_index), str(chunk_index))
# find lowest
sector = self.sectors[sector_index]
print('considering', str(sector))
if sector.allocated():
print('allocated sector', str(sector))
if sector.chunk.index == chunk_index:
# good don't need to move it
print(str(sector), 'in correct place')
else:
print('going to move', sector.chunk.index)
# move it to the end
endIndex = self.highest_unallocated_sector_index()
print('temp moving to end index', endIndex)
self.move_chunk(sector_index, SECTOR_FREE, endIndex, SECTOR_TEMPORARY)
# find the sector with the matching file index
# move it to this spot and deallocate it from
# current spot
print('looking for item', str(chunk_index), 'to put into', str(sector_index))
for i in range(len(self.sectors)):
nextSector = self.sectors[i]
if nextSector.allocated() and nextSector.chunk.index == chunk_index:
self.move_chunk(i, SECTOR_FREE, sector_index, SECTOR_ALLOCATED)
else:
# need to find one to move here.
print('looking for item', str(chunk_index), 'to put into', str(sector_index))
for i in range(len(self.sectors)):
nextSector = self.sectors[i]
if nextSector.allocated() and nextSector.chunk.index == chunk_index:
self.move_chunk(i, SECTOR_FREE, sector_index, SECTOR_ALLOCATED)
except exception as e:
print(e)
I also added some colouring to the temporarily displaced files to make it easier to see progress in terms of chunks that are still to be processed, those that are in place, and those that have been moved to make way for others.
Glue
I made the frame rate increase slightly to make the animation work a bit better. I also added quite a lot of "I am here" debug statements since it didn't take much to completely break the sketch and leave me with a blank canvas.
def setup():
global fat, files
size(1000, 800)
fat = FileAllocationTable(800)
files = []
number_of_files = int(random(10, 75))
index = 0
for i in range(number_of_files):
file = File(i, index)
files.append(file)
index += file.size()
fat.scatter(files)
frameRate(5)
sector_index = 0
chunk_index = 0
def draw():
global sector_index, chunk_index
fat.draw()
if sector_index < 800:
print('defragging', sector_index)
fat.defrag(sector_index, chunk_index)
sector_index += 1
chunk_index += 1
I keep track of the current/next chunk to process using the index variables in the draw function.