Files
gtav-src/tools_ng/lib/util/DiskTools/disktools.rb
T
2025-09-29 00:52:08 +02:00

555 lines
17 KiB
Ruby
Executable File

# File:: disktools.rb
# Description:: statistically analyses the 360 disk emulation output
#
# Author:: Derek Ward <derek.ward@rockstarnorth.com>
# 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, <volume descriptor>, <empty>
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