# File:: disktools.rb # Description:: statistically analyses the 360 disk emulation output # # Author:: Derek Ward # Date:: 04 Septmeber 2009 # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- require 'win32ole' require 'pipeline/os/path' require 'time' require 'pipeline/os/getopt' require 'pipeline/os/path' require 'fileutils' include FileUtils require 'dl' include Pipeline require 'pipeline/log/log' #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- OPTIONS = [ [ '--xlsfilename', '-x', Getopt::REQUIRED, 'specify xls filename to process' ], [ '--trylayerswap', '-l', Getopt::REQUIRED, 'tries to optimise for layer swapping' ], [ '--checkthruput', '-t', Getopt::OPTIONAL, 'check the throughput of data' ] ] BUTTONS_OK = 0 BUTTONS_OKCANCEL = 1 BUTTONS_ABORTRETRYIGNORE = 2 BUTTONS_YESNO = 4 CLICKED_OK = 1 CLICKED_CANCEL = 2 CLICKED_ABORT = 3 CLICKED_RETRY = 4 CLICKED_IGNORE = 5 CLICKED_YES = 6 CLICKED_NO = 7 ICON_HAND = 16 ICON_QUESTION = 32 ICON_EXCLAMATION = 48 ICON_ASTERISK = 64 #----------------------------------------------------------------------------- # Implementation #----------------------------------------------------------------------------- module DiskTools #--------------------------------------------------------------------- def DiskTools::message_box(txt='', title='', buttons=0, icon=0) user32 = DL.dlopen('user32') msgbox = user32['MessageBoxA', 'ILSSI'] r, rs = msgbox.call(0, txt, title, buttons+icon) return r end #--------------------------------------------------------------------- # class repository for Excel constants involved in Win32OLE calls. class ExcelConst def ExcelConst::filled=(val) @@filled = val end def ExcelConst::filled() @@filled end private @@filled = false end #--------------------------------------------------------------------- # class repository for MSO constants involved in Win32OLE calls. class MSOConst def MSOConst::filled=(val) @@filled = val end def MSOConst::filled() @@filled end private @@filled = false end #--------------------------------------------------------------------- class DiskFiles attr_accessor :files def initialize() @files = {} end def pretty_print() puts "\nDISK FILES (#{@files.length})" @files.each { |key, file| file.pretty_print } end end #--------------------------------------------------------------------- class DiskFile attr_accessor :filename attr_accessor :size attr_accessor :layer attr_accessor :layer_swaps attr_accessor :locked attr_accessor :times_read attr_accessor :total_seek attr_accessor :total_read attr_accessor :total_blocks attr_accessor :total_actual def initialize(filename,size,layer,layer_swaps,locked,times_read,total_seek,total_read,total_blocks,total_actual) @filename = filename @size = size @layer = layer @layer_swaps = layer_swaps @locked = locked @times_read = times_read @total_seek = total_seek @total_read = total_read @total_blocks = total_blocks @total_actual = total_actual end def pretty_print(title="Files", msgbox = false) fn = filename[0,60] fn = fn.ljust(60) siz = @size.to_s.ljust(10) ls = @layer_swaps.to_s.ljust(4) tr = @times_read.to_i.to_s.ljust(5) tots = @total_seek.to_i.to_s.ljust(10) totr = @total_read.to_i.to_s.ljust(10) totb = @total_blocks.to_s.ljust(5) tota = @total_actual.to_i.to_s.ljust(10) str = "#{fn} Lyr #{@layer} LyrSwap #{ls} xRead #{tr} Seek #{tots} Read #{totr} Blks #{totb} Act #{tota}" puts str DiskTools::message_box( str, title, BUTTONS_OK, ICON_EXCLAMATION) if msgbox end end #--------------------------------------------------------------------- #Time(ms) Operation Seek(us) Read(us) Lyr Start LBA Blocks Actual(us) File(s)Accessed class DiskEvent attr_accessor :time attr_accessor :operation attr_accessor :seek attr_accessor :read attr_accessor :layer attr_accessor :start_lba attr_accessor :blocks attr_accessor :actual attr_accessor :filename attr_accessor :locked attr_accessor :dest_layer def initialize(time,operation,seek,read,layer,start_lba,blocks,actual,filename,locked,dest_layer) @time = time @operation = operation @seek = seek @read = read @layer = layer @start_lba = start_lba @blocks = blocks @actual = actual @filename = filename @locked = locked @dest_layer = dest_layer end def pretty_print() puts "time #{@time} operation #{@operation} seek #{@seek} read #{@read} layer #{@layer} start_lba #{@start_lba} blocks #{@blocks} actual #{@actual} filename #{@filename} locked #{@locked} dest_layer #{@dest_layer}" end end #--------------------------------------------------------------------- class DiskEvents attr_accessor :events def initialize() @events = Array.new end def pretty_print() puts "\nDISK EVENTS (#{@events.length})" @events.each { |de| de.pretty_print } end end #--------------------------------------------------------------------- class Stats attr_accessor :total_layer_flips def initialize(events, files) @total_layer_flips = 0 len = events.events.length events.events.each_with_index do |event, index| curr_layer = event.layer prev_layer = event.layer next_layer = event.layer prev_layer = events.events[index-1].layer if index > 0 next_layer = events.events[index+1].layer if index < len-2 @total_layer_flips += 1 if next_layer != curr_layer end end def pretty_print(title = "Stats", msgbox = true) str = "\nTotal Layer Flips #{@total_layer_flips} (#{total_layer_flips*75}ms)" puts str DiskTools::message_box( str, title, BUTTONS_OK, ICON_EXCLAMATION) if msgbox end end #--------------------------------------------------------------------- # == Description # read the whole worksheet into my diskevents class def DiskTools::ReadSheet(worksheet, disk_events) data = worksheet.UsedRange.Value num_rows = data.size num_columns = data[0].size puts "num rows #{num_rows} num columns #{num_columns}\n" row = 1 column = 1 num_columns.times { |c| puts worksheet.cells(row,column+c)['Value'] } row += 1 max_lines_to_read = 99999 puts "Interpreting worksheet...#{max_lines_to_read} lines" # Nuts... this is really too slow in ruby max_lines_to_read.times do val = worksheet.cells(row, column)['Value'] break unless val #hh:mm:ss:msec, READ, LBA, Count, Read(ms), Seek(ms), Actual(ms), Emulator Delay(ms), Files... #00:00:01:522.1, READ, 32, 16, 5.6, 80.6, 86.2, -0.0, , time = worksheet.cells(row, column)['Value'] operation = worksheet.cells(row, column+1)['Value'] start_lba = worksheet.cells(row, column+2)['Value'].to_i blocks = worksheet.cells(row, column+3)['Value'].to_i read = worksheet.cells(row, column+4)['Value'].to_f seek = worksheet.cells(row, column+5)['Value'].to_f actual = worksheet.cells(row, column+6)['Value'].to_f emu_delay = worksheet.cells(row, column+7)['Value'].to_f filename = worksheet.cells(row, column+8)['Value'] layer = 0 # ahh boo why do they no longer give you this? times = time.split(":") time = times[0].to_i * 60 * 60 * 1000 time += times[1].to_i * 60 * 1000 time += times[2].to_i * 1000 times2 = times[3].split(".") time += times2[0].to_i #Time(ms) Operation Seek(us) Read(us) Lyr Start LBA Blocks Actual(us) File(s)Accessed #time = worksheet.cells(row, column)['Value'].to_i #operation = worksheet.cells(row, column+1)['Value'] #seek = worksheet.cells(row, column+2)['Value'].to_i #read = worksheet.cells(row, column+3)['Value'].to_i #layer = worksheet.cells(row, column+4)['Value'].to_i #start_lba = worksheet.cells(row, column+5)['Value'].to_i #blocks = worksheet.cells(row, column+6)['Value'].to_i #actual = worksheet.cells(row, column+7)['Value'].to_i #filename = worksheet.cells(row, column+8)['Value'] locked = 0#worksheet.cells(row, column+9)['Value'].to_i dest_layer = 0#worksheet.cells(row, column+10)['Value'].to_i disk_events.events << DiskEvent.new(time,operation,seek,read,layer,start_lba,blocks,actual,filename,locked,dest_layer) row += 1 end puts "Interpreting worksheet complete" end # # == Description def DiskTools::BuildFiles(events, files) layer_swaps = 0 len = events.events.length events.events.each_with_index do |event, index| if files.files.has_key?(event.filename) then files.files[event.filename].times_read += 1 files.files[event.filename].total_seek += event.seek files.files[event.filename].total_read += event.read files.files[event.filename].total_blocks += event.blocks files.files[event.filename].total_actual += event.actual curr_layer = event.layer prev_layer = event.layer next_layer = event.layer prev_layer = events.events[index-1].layer if index > 0 next_layer = events.events[index+1].layer if index < len-2 if (prev_layer!=curr_layer) then files.files[event.filename].layer_swaps += 1 end if (next_layer!=curr_layer) files.files[event.filename].layer_swaps += 1 end else filename = event.filename size = 0 layer = event.layer locked = event.locked times_read = 1 total_seek = event.seek total_read = event.read total_blocks = event.blocks total_actual = event.actual files.files[event.filename] = DiskFile.new(filename,size,layer,layer_swaps,locked,times_read,total_seek,total_read,total_blocks,total_actual) end end end # # == Description def DiskTools::GetWorstLayerSwappingFile(events, files) worst_layer_swaps = 0 worst_file_key = 0 files.files.each do |key, file| if (file.layer_swaps > worst_layer_swaps) then worst_layer_swaps = file.layer_swaps worst_file_key = key end end files.files[worst_file_key] end # # == Description def DiskTools::FlipFileLayer(events, files, file) return if file.locked == 1 events.events.each_with_index do |event, index| if (event.filename == file.filename) then if event.layer == 0 then event.layer = 1 else event.layer = 0 end end end end def DiskTools::Proposal(stats,new_stats,file) response = CLICKED_NO saving = stats.total_layer_flips - new_stats.total_layer_flips if (saving>0) then str = "Flipping file #{file.filename} on layer #{file.layer} would save #{saving} layer swaps (#{saving*75}ms)" puts str response = message_box( "#{str}","Flip Layer?", BUTTONS_YESNO, ICON_QUESTION) else message_box( "No point in flipping #{file.filename} Flips saved would be #{saving}","No point", BUTTONS_OK, ICON_EXCLAMATION) end response end # # == Description # analyse the xls file def DiskTools::Process(worksheet, try_layer_swap, checkthruput) puts "*************" puts " INIT " puts "*************\n" disk_files = DiskFiles.new() disk_events = DiskEvents.new() DiskTools::ReadSheet(worksheet, disk_events) disk_events.pretty_print DiskTools::BuildFiles(disk_events, disk_files) disk_files.pretty_print orig_stats = Stats.new(disk_events, disk_files) puts "*************" puts " PROCESSING " puts "*************\n" if (try_layer_swap==1) then disk_files.files.each do |key, file| stats = Stats.new(disk_events, disk_files) FlipFileLayer(disk_events, disk_files, file) new_stats = Stats.new(disk_events, disk_files) flip_it = Proposal(stats,new_stats,file) == CLICKED_YES FlipFileLayer(disk_events, disk_files, file) unless flip_it puts "#{file.filename} flipped" if flip_it end end if (0) then# then checkthruput==1) then firstTime = disk_events.events[0].time lastTime = disk_events.events[-1].time total_time = lastTime-firstTime puts "Total Time : #{total_time}ms" total_actual = 0 total_blocks = 0 total_reads = 0 total_events = 0 disk_events.events.each_with_index do |event, index| total_actual += event.actual total_blocks += event.blocks total_reads += event.read total_events += 1 end puts "total_actual : #{total_actual}" puts "total_blocks : #{total_blocks}" puts "total_reads : #{total_reads}" puts "total_events : #{total_events}" puts "total_events/s : #{(total_events.to_f/total_time.to_f) * 1000.0}" puts "total_blocks/s : #{(total_blocks.to_f/total_time.to_f) * 1000.0}" data_thru = total_blocks*2 mbpers = (data_thru.to_f/total_time.to_f)/1024.0 puts "thus Mb/s : #{mbpers}" end puts "*************" puts "FINAL RESULTS" puts "*************\n" #disk_events.pretty_print final_disk_files = DiskFiles.new() puts final_disk_files.class puts final_disk_files.files.length DiskTools::BuildFiles(disk_events, final_disk_files) final_disk_files.pretty_print orig_stats.pretty_print("Original stats") puts "=>" new_stats = Stats.new(disk_events, final_disk_files) new_stats.pretty_print("Final Stats") end # # == Description # analyse the xls file def DiskTools::analyse_file(xls_filename, try_layer_swap, checkthruput) begin puts "File not found #{xls_filename}" and return '' if not File.exists?(xls_filename) if File::exists?( xls_filename ) then begin puts "Opening #{xls_filename}" puts "Staring Excel" begin excel = WIN32OLE.connect("excel.application") rescue puts " * A new instance of Excel has been started, if you see this message more than once it may be a leaking process! * " excel = WIN32OLE.new("excel.application") if excel==nil end excel.DisplayAlerts = true WIN32OLE.const_load(excel, ExcelConst) if not ExcelConst::filled ExcelConst::filled = true #WIN32OLE.const_load('Microsoft Office 9.0 Object Library', MSOConst) if not MSOConst::filled #MSOConst::filled = true #excel.visible = true # in case you want to see what happens puts "#{xls_filename}" workbook = excel.Workbooks.Open(xls_filename); #OpenXML(xls_filename,nil,ExcelConst::XlXmlLoadImportToList) worksheet = workbook.Worksheets(1) worksheet.Activate Process(worksheet, try_layer_swap, checkthruput) #workbook.saveas(xls_filename) if save_xls workbook.close rescue Exception => ex puts "WIN32OLE Excel stuff has quit unexpectedly:" puts "\t#{ex.message}" ex.backtrace.each do |m| puts "\t#{m}"; end excel.Quit() ensure excel.Quit() unless excel.nil? end # end begin 1 excel.Quit() unless excel.nil? end # end if file exists rescue Exception => ex puts "DiskTools::analyse_file has quit unexpectedly as the file doesnt exist" puts "\t#{ex.message}" ex.backtrace.each do |m| puts "\t#{m}"; end end puts "File Analysed" end # analyse_file #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- begin #------------------------------------------------------------------------- # Entry-Point #------------------------------------------------------------------------- g_AppName = File::basename( __FILE__, '.rb' ) g_xls_filename = '' g_log = Log.new( 'disktools' ) #------------------------------------------------------------------------- # Parse Command Line #------------------------------------------------------------------------- opts, trailing = OS::Getopt.getopts( OPTIONS ) if ( opts['help'] ) then puts OS::Getopt.usage( OPTIONS ) response = message_box( "#{g_AppName} will exit.",g_AppName, BUTTONS_OK, ICON_QUESTION) exit( 1 ) end g_xls_filename = ( nil == opts['xlsfilename'] ) ? '' : opts['xlsfilename'] g_try_layer_swap = ( nil == opts['trylayerswap'] ) ? '' : opts['trylayerswap'] g_check_thruput = ( nil == opts['checkthruput'] ) ? '' : opts['checkthruput'] response = message_box( "Ready to load #{g_xls_filename}?",g_AppName, BUTTONS_OK, ICON_QUESTION) #exit( 0 ) if response != BUTTONS_OK puts "analyse_file #{g_xls_filename}" DiskTools::analyse_file(g_xls_filename, g_try_layer_swap, g_check_thruput) rescue Exception => ex puts "#{g_AppName} unhandled exception: #{ex.message}" puts "Call stack:" puts ex.backtrace.join( "\n\t" ) end end # Disktools module # Disktools.rb