《操作系统》的实验代码。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
17 KiB

  1. #! /usr/bin/env python
  2. import math
  3. import random
  4. from optparse import OptionParser
  5. # minimum unit of transfer to RAID
  6. BLOCKSIZE = 4096
  7. def convert(size):
  8. length = len(size)
  9. lastchar = size[length-1]
  10. if (lastchar == 'k') or (lastchar == 'K'):
  11. m = 1024
  12. nsize = int(size[0:length-1]) * m
  13. elif (lastchar == 'm') or (lastchar == 'M'):
  14. m = 1024*1024
  15. nsize = int(size[0:length-1]) * m
  16. elif (lastchar == 'g') or (lastchar == 'G'):
  17. m = 1024*1024*1024
  18. nsize = int(size[0:length-1]) * m
  19. else:
  20. nsize = int(size)
  21. return nsize
  22. class disk:
  23. def __init__(self, seekTime=10, xferTime=0.1, queueLen=8):
  24. # these are both in milliseconds
  25. # seek is the time to seek (simple constant amount)
  26. # transfer is the time to read one block
  27. self.seekTime = seekTime
  28. self.xferTime = xferTime
  29. # length of scheduling queue
  30. self.queueLen = queueLen
  31. # current location: make it negative so that whatever
  32. # the first read is, it causes a seek
  33. self.currAddr = -10000
  34. # queue
  35. self.queue = []
  36. # disk geometry
  37. self.numTracks = 100
  38. self.blocksPerTrack = 100
  39. self.blocksPerDisk = self.numTracks * self.blocksPerTrack
  40. # stats
  41. self.countIO = 0
  42. self.countSeq = 0
  43. self.countNseq = 0
  44. self.countRand = 0
  45. self.utilTime = 0
  46. def stats(self):
  47. return (self.countIO, self.countSeq, self.countNseq, self.countRand, self.utilTime)
  48. def enqueue(self, addr):
  49. assert(addr < self.blocksPerDisk)
  50. self.countIO += 1
  51. # check if this is on the same track, or a different one
  52. currTrack = self.currAddr / self.numTracks
  53. newTrack = addr / self.numTracks
  54. # absolute diff
  55. diff = addr - self.currAddr
  56. # if on the same track...
  57. if currTrack == newTrack or diff < self.blocksPerTrack:
  58. if diff == 1:
  59. self.countSeq += 1
  60. else:
  61. self.countNseq += 1
  62. self.utilTime += (diff * self.xferTime)
  63. else:
  64. self.countRand += 1
  65. self.utilTime += (self.seekTime + self.xferTime)
  66. self.currAddr = addr
  67. def go(self):
  68. return self.utilTime
  69. class raid:
  70. def __init__(self, chunkSize='4k', numDisks=4, level=0, timing=False, reverse=False, solve=False, raid5type='LS'):
  71. chunkSize = int(convert(chunkSize))
  72. self.chunkSize = chunkSize / BLOCKSIZE
  73. self.numDisks = numDisks
  74. self.raidLevel = level
  75. self.timing = timing
  76. self.reverse = reverse
  77. self.solve = solve
  78. self.raid5type = raid5type
  79. if (chunkSize % BLOCKSIZE) != 0:
  80. print 'chunksize (%d) must be multiple of blocksize (%d): %d' % (chunkSize, BLOCKSIZE, self.chunkSize % BLOCKSIZE)
  81. exit(1)
  82. if self.raidLevel == 1 and numDisks % 2 != 0:
  83. print 'raid1: disks (%d) must be a multiple of two' % numDisks
  84. exit(1)
  85. if self.raidLevel == 4:
  86. self.blocksInStripe = (self.numDisks - 1) * self.chunkSize
  87. self.pdisk = self.numDisks - 1
  88. if self.raidLevel == 5:
  89. self.blocksInStripe = (self.numDisks - 1) * self.chunkSize
  90. self.pdisk = -1
  91. self.disks = []
  92. for i in range(self.numDisks):
  93. self.disks.append(disk())
  94. # print per-disk stats
  95. def stats(self, totalTime):
  96. for d in range(self.numDisks):
  97. s = self.disks[d].stats()
  98. if s[4] == totalTime:
  99. print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
  100. elif s[4] == 0:
  101. print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
  102. else:
  103. print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
  104. # global enqueue function
  105. def enqueue(self, addr, size, isWrite):
  106. # should we print out the logical operation?
  107. if self.timing == False:
  108. if self.solve or self.reverse==False:
  109. if isWrite:
  110. print 'LOGICAL WRITE to addr:%d size:%d' % (addr, size * BLOCKSIZE)
  111. else:
  112. print 'LOGICAL READ from addr:%d size:%d' % (addr, size * BLOCKSIZE)
  113. if self.solve == False:
  114. print ' Physical reads/writes?\n'
  115. else:
  116. print 'LOGICAL OPERATION is ?'
  117. # should we print out the physical operations?
  118. if self.timing == False and (self.solve or self.reverse==True):
  119. self.printPhysical = True
  120. else:
  121. self.printPhysical = False
  122. if self.raidLevel == 0:
  123. self.enqueue0(addr, size, isWrite)
  124. elif self.raidLevel == 1:
  125. self.enqueue1(addr, size, isWrite)
  126. elif self.raidLevel == 4 or self.raidLevel == 5:
  127. self.enqueue45(addr, size, isWrite)
  128. # process disk workloads one at a time, returning final completion time
  129. def go(self):
  130. tmax = 0
  131. for d in range(self.numDisks):
  132. # print '**** disk ****', d
  133. t = self.disks[d].go()
  134. if t > tmax:
  135. tmax = t
  136. return tmax
  137. # helper functions
  138. def doSingleRead(self, disk, off, doNewline=False):
  139. if self.printPhysical:
  140. print ' read [disk %d, offset %d] ' % (disk, off),
  141. if doNewline:
  142. print ''
  143. self.disks[disk].enqueue(off)
  144. def doSingleWrite(self, disk, off, doNewline=False):
  145. if self.printPhysical:
  146. print ' write [disk %d, offset %d] ' % (disk, off),
  147. if doNewline:
  148. print ''
  149. self.disks[disk].enqueue(off)
  150. #
  151. # mapping for RAID 0 (striping)
  152. #
  153. def bmap0(self, bnum):
  154. cnum = bnum / self.chunkSize
  155. coff = bnum % self.chunkSize
  156. return (cnum % self.numDisks, (cnum / self.numDisks) * self.chunkSize + coff)
  157. def enqueue0(self, addr, size, isWrite):
  158. # can ignore isWrite, as I/O pattern is the same for striping
  159. for b in range(addr, addr+size):
  160. (disk, off) = self.bmap0(b)
  161. if isWrite:
  162. self.doSingleWrite(disk, off, True)
  163. else:
  164. self.doSingleRead(disk, off, True)
  165. if self.timing == False and self.printPhysical:
  166. print ''
  167. #
  168. # mapping for RAID 1 (mirroring)
  169. #
  170. def bmap1(self, bnum):
  171. cnum = bnum / self.chunkSize
  172. coff = bnum % self.chunkSize
  173. disk = 2 * (cnum % (self.numDisks / 2))
  174. return (disk, disk + 1, (cnum / (self.numDisks / 2)) * self.chunkSize + coff)
  175. def enqueue1(self, addr, size, isWrite):
  176. for b in range(addr, addr+size):
  177. (disk1, disk2, off) = self.bmap1(b)
  178. # print 'enqueue:', addr, size, '-->', m
  179. if isWrite:
  180. self.doSingleWrite(disk1, off, False)
  181. self.doSingleWrite(disk2, off, True)
  182. else:
  183. # the raid-1 read balancing algorithm is here;
  184. # could be something more intelligent --
  185. # instead, it is just based on the disk offset
  186. # to produce something easily reproducible
  187. if off % 2 == 0:
  188. self.doSingleRead(disk1, off, True)
  189. else:
  190. self.doSingleRead(disk2, off, True)
  191. if self.timing == False and self.printPhysical:
  192. print ''
  193. #
  194. # mapping for RAID 4 (parity disk)
  195. #
  196. # assumes (for now) that there is just one parity disk
  197. #
  198. def bmap4(self, bnum):
  199. cnum = bnum / self.chunkSize
  200. coff = bnum % self.chunkSize
  201. return (cnum % (self.numDisks - 1), (cnum / (self.numDisks - 1)) * self.chunkSize + coff)
  202. def pmap4(self, snum):
  203. return self.pdisk
  204. #
  205. # mapping for RAID 5 (rotated parity)
  206. #
  207. def __bmap5(self, bnum):
  208. cnum = bnum / self.chunkSize
  209. coff = bnum % self.chunkSize
  210. ddsk = cnum / (self.numDisks - 1)
  211. doff = (ddsk * self.chunkSize) + coff
  212. disk = cnum % (self.numDisks - 1)
  213. col = (ddsk % self.numDisks)
  214. pdsk = (self.numDisks - 1) - col
  215. # supports left-asymmetric and left-symmetric layouts
  216. if self.raid5type == 'LA':
  217. if disk >= pdisk:
  218. disk += 1
  219. elif self.raid5type == 'LS':
  220. disk = (disk - col) % (self.numDisks)
  221. else:
  222. print 'error: no such RAID scheme'
  223. exit(1)
  224. assert(disk != pdsk)
  225. return (disk, pdsk, doff)
  226. # yes this is lame (redundant call to __bmap5 is serious programmer laziness)
  227. def bmap5(self, bnum):
  228. (disk, pdisk, off) = self.__bmap5(bnum)
  229. return (disk, off)
  230. # this too is lame (redundant call to __bmap5 is serious programmer laziness)
  231. def pmap5(self, snum):
  232. (disk, pdisk, off) = self.__bmap5(snum * self.blocksInStripe)
  233. return pdisk
  234. # RAID 4/5 helper routine to write out some blocks in a stripe
  235. def doPartialWrite(self, stripe, begin, end, bmap, pmap):
  236. numWrites = end - begin
  237. pdisk = pmap(stripe)
  238. if (numWrites + 1) <= (self.blocksInStripe - numWrites):
  239. # SUBTRACTIVE PARITY
  240. # print 'SUBTRACTIVE'
  241. offList = []
  242. for voff in range(begin, end):
  243. (disk, off) = bmap(voff)
  244. self.doSingleRead(disk, off)
  245. if off not in offList:
  246. offList.append(off)
  247. for i in range(len(offList)):
  248. self.doSingleRead(pdisk, offList[i], i == (len(offList) - 1))
  249. else:
  250. # ADDITIVE PARITY
  251. # print 'ADDITIVE'
  252. stripeBegin = stripe * self.blocksInStripe
  253. stripeEnd = stripeBegin + self.blocksInStripe
  254. for voff in range(stripeBegin, begin):
  255. (disk, off) = bmap(voff)
  256. self.doSingleRead(disk, off, (voff == (begin - 1)) and (end == stripeEnd))
  257. for voff in range(end, stripeEnd):
  258. (disk, off) = bmap(voff)
  259. self.doSingleRead(disk, off, voff == (stripeEnd - 1))
  260. # WRITES: same for additive or subtractive parity
  261. offList = []
  262. for voff in range(begin, end):
  263. (disk, off) = bmap(voff)
  264. self.doSingleWrite(disk, off)
  265. if off not in offList:
  266. offList.append(off)
  267. for i in range(len(offList)):
  268. self.doSingleWrite(pdisk, offList[i], i == (len(offList) - 1))
  269. # RAID 4/5 enqueue routine
  270. def enqueue45(self, addr, size, isWrite):
  271. if self.raidLevel == 4:
  272. (bmap, pmap) = (self.bmap4, self.pmap4)
  273. elif self.raidLevel == 5:
  274. (bmap, pmap) = (self.bmap5, self.pmap5)
  275. if isWrite == False:
  276. for b in range(addr, addr+size):
  277. (disk, off) = bmap(b)
  278. self.doSingleRead(disk, off)
  279. else:
  280. # process the write request, one stripe at a time
  281. initStripe = (addr) / self.blocksInStripe
  282. finalStripe = (addr + size - 1) / self.blocksInStripe
  283. left = size
  284. begin = addr
  285. for stripe in range(initStripe, finalStripe + 1):
  286. endOfStripe = (stripe * self.blocksInStripe) + self.blocksInStripe
  287. if left >= self.blocksInStripe:
  288. end = begin + self.blocksInStripe
  289. else:
  290. end = begin + left
  291. if end >= endOfStripe:
  292. end = endOfStripe
  293. self.doPartialWrite(stripe, begin, end, bmap, pmap)
  294. left -= (end - begin)
  295. begin = end
  296. # for all cases, print this for pretty-ness in mapping mode
  297. if self.timing == False and self.printPhysical:
  298. print ''
  299. #
  300. # main program
  301. #
  302. parser = OptionParser()
  303. parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
  304. parser.add_option('-D', '--numDisks', default=4, help='number of disks in RAID', action='store', type='int', dest='numDisks')
  305. parser.add_option('-C', '--chunkSize', default='4k', help='chunk size of the RAID', action='store', type='string', dest='chunkSize')
  306. parser.add_option('-n', '--numRequests', default=10, help='number of requests to simulate', action='store', type='int', dest='numRequests')
  307. parser.add_option('-S', '--reqSize', default='4k', help='size of requests', action='store', type='string', dest='size')
  308. parser.add_option('-W', '--workload', default='rand', help='either "rand" or "seq" workloads', action='store', type='string', dest='workload')
  309. parser.add_option('-w', '--writeFrac', default=0, help='write fraction (100->all writes, 0->all reads)', action='store', type='int', dest='writeFrac')
  310. parser.add_option('-R', '--randRange', default=10000, help='range of requests (when using "rand" workload)', action='store', type='int', dest='range')
  311. parser.add_option('-L', '--level', default=0, help='RAID level (0, 1, 4, 5)', action='store', type='int', dest='level')
  312. parser.add_option('-5', '--raid5', default='LS', help='RAID-5 left-symmetric "LS" or left-asym "LA"', action='store', type='string', dest='raid5type')
  313. parser.add_option('-r', '--reverse', default=False, help='instead of showing logical ops, show physical', action='store_true', dest='reverse')
  314. parser.add_option('-t', '--timing', default=False, help='use timing mode, instead of mapping mode', action='store_true', dest='timing')
  315. parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
  316. (options, args) = parser.parse_args()
  317. print 'ARG blockSize', BLOCKSIZE
  318. print 'ARG seed', options.seed
  319. print 'ARG numDisks', options.numDisks
  320. print 'ARG chunkSize', options.chunkSize
  321. print 'ARG numRequests', options.numRequests
  322. print 'ARG reqSize', options.size
  323. print 'ARG workload', options.workload
  324. print 'ARG writeFrac', options.writeFrac
  325. print 'ARG randRange', options.range
  326. print 'ARG level', options.level
  327. print 'ARG raid5', options.raid5type
  328. print 'ARG reverse', options.reverse
  329. print 'ARG timing', options.timing
  330. print ''
  331. writeFrac = float(options.writeFrac) / 100.0
  332. assert(writeFrac >= 0.0 and writeFrac <= 1.0)
  333. random.seed(options.seed)
  334. size = convert(options.size)
  335. if size % BLOCKSIZE != 0:
  336. print 'error: request size (%d) must be a multiple of BLOCKSIZE (%d)' % (size, BLOCKSIZE)
  337. exit(1)
  338. size = size / BLOCKSIZE
  339. if options.workload == 'seq' or options.workload == 's' or options.workload == 'sequential':
  340. workloadIsSequential = True
  341. elif options.workload == 'rand' or options.workload == 'r' or options.workload == 'random':
  342. workloadIsSequential = False
  343. else:
  344. print 'error: workload must be either r/rand/random or s/seq/sequential'
  345. exit(1)
  346. assert(options.level == 0 or options.level == 1 or options.level == 4 or options.level == 5)
  347. if options.level != 0 and options.numDisks < 2:
  348. print 'RAID-4 and RAID-5 need more than 1 disk'
  349. exit(1)
  350. if options.level == 5 and options.raid5type != 'LA' and options.raid5type != 'LS':
  351. print 'Only two types of RAID-5 supported: left-asymmetric (LA) and left-symmetric (LS) (%s is not)' % options.raid5type
  352. exit(1)
  353. # instantiate RAID
  354. r = raid(chunkSize=options.chunkSize, numDisks=options.numDisks, level=options.level, timing=options.timing,
  355. reverse=options.reverse, solve=options.solve, raid5type=options.raid5type)
  356. # generate requests
  357. off = 0
  358. for i in range(options.numRequests):
  359. if workloadIsSequential == True:
  360. blk = off
  361. off += size
  362. else:
  363. blk = int(random.random() * options.range)
  364. if random.random() < writeFrac:
  365. r.enqueue(blk, size, True)
  366. else:
  367. r.enqueue(blk, size, False)
  368. # process requests
  369. t = r.go()
  370. # print out some final info, if needed
  371. if options.timing == False:
  372. print ''
  373. exit(0)
  374. if options.solve:
  375. print ''
  376. r.stats(t)
  377. print ''
  378. print 'STAT totalTime', t
  379. print ''
  380. else:
  381. print ''
  382. print 'Estimate how long the workload should take to complete.'
  383. print '- Roughly how many requests should each disk receive?'
  384. print '- How many requests are random, how many sequential?'
  385. print ''