-
Notifications
You must be signed in to change notification settings - Fork 0
/
dd-opt.rb
203 lines (171 loc) · 5.74 KB
/
dd-opt.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
require 'os'
class BlockDevice
attr_reader :path
attr_reader :mounted
attr_reader :total_bytes
attr_reader :free_bytes
def initialize(path)
if !FileTest.chardev?(path) && !FileTest.blockdev?(path)
abort(path + " is not a block device, raw device or character device.")
else
@path = path
## Initialise other BlockDevice attributes.
if OS::mac?
## Get BlockDevice info string
@info = `diskutil info #{@path}`
## Initialise @mounted attribute
if @info.scan(/Mounted:\s*(\w*)/)[0][0] == "Yes" then @mounted = true
elsif @info.scan(/Mounted:\s*(\w*)/)[0][0] == "No" then @mounted = false
else abort("Could not determine whether " + @path + " is mounted.")
end
## Initialise @total_bytes attribute
@total_bytes = @info.scan(/Total Size.*\((\d*) Bytes\)/)[0][0].to_i
## Initialise @free_bytes attribute
# Mac OS X diskutil will not report the free space on an unmounted
# block device so we must mount this BlockDevice temporarily to find its
# free space and then immediately unmount it again to restore its state
# unless it was already mounted.
if !@mounted
if !system("diskutil mount " + @path)
abort ("Could not mount " + @path)
end
@free_bytes = @info.scan(/Volume Free Space.*\((\d*) Bytes\)/)[0][0].to_i
if !system("diskutil unmount " + @path)
abort ("Could not unmount " + @path)
end
else
@free_bytes = @info.scan(/Volume Free Space.*\((\d*) Bytes\)/)[0][0].to_i
end
else
abort("dd-opt does not yet work on operating systems besides Mac OS X. Help out by forking https://github.com/sampablokuper/dd-opt")
end
end
end
end
##
# Parse options and arguments
##
help = "\nUsage: dd-opt.rb dd-infile dd-outfile [MAXBS] [ITERATIONS]
Example 1: dd-opt.rb /dev/disk4s1 /dev/disk5s1 16m 3
Or, using raw devices for faster speed:
Example 2: dd-opt.rb /dev/rdisk4s1 /dev/rdisk5s1 16m 3
*** Important note: dd-outfile will be partially or wholly overwritten. ***
'-h', '--help', Display this screen.
MAXBS Maximum value of dd command's 'bs' parameter to try, e.g. 512 or
4k. Valid suffixes: k, m, g, t, p, e, z, y.
ITERATIONS Number of times to iterate over all values of bs
incrementing in powers of 2 upto MAXBS.\n\n"
if ARGV.length < 1
abort("No dd infile specified.")
elsif ARGV.length < 2
if ARGV[0] == "-h" || ARGV[0] == "--help"
puts help; exit
else
abort("No dd outfile specified.")
end
elsif ARGV.length > 4
abort("Too many arguments.")
elsif ARGV.length == 2
ddbsmax = "4k"
iterations = "3"
elsif ARGV.length == 3
ddbsmax = ARGV[2]
iterations = "3"
elsif ARGV.length == 4
ddbsmax = ARGV[2]
iterations = ARGV[3]
end
ddif = BlockDevice.new(ARGV[0])
ddof = BlockDevice.new(ARGV[1])
##
# Validate ddbsmax
##
ddbsmax_array = ddbsmax.scan(/(\D*)(\d*)(.*)/)[0]
if ddbsmax_array[0] != ""
abort("'" + ddbsmax + "' is not a valid maximum block size")
end
ddbsmax_n = ddbsmax_array[1].to_i
ddbsmax_suffix = ddbsmax_array[2]
if ddbsmax_suffix == "b" || ddbsmax_suffix == ""
power = 0
elsif ddbsmax_suffix == "k"
power = 10
elsif ddbsmax_suffix == "m"
power = 20
elsif ddbsmax_suffix == "g"
power = 30
elsif ddbsmax_suffix == "t"
power = 40
elsif ddbsmax_suffix == "p"
power = 50
elsif ddbsmax_suffix == "e"
power = 60
elsif ddbsmax_suffix == "z"
power = 70
elsif ddbsmax_suffix == "y"
power = 80
else
abort("Invalid block size suffix.")
end
ddbsmax_bytes = ddbsmax_n * 2 ** power
ddbsmax_bytes_length = ddbsmax_bytes.to_s.length
ddbs = 1
until ddbs > ddbsmax_bytes
ddbs = ddbs * 2
end
effective_ddbsmax_bytes = ddbs / 2
if effective_ddbsmax_bytes > ddif.total_bytes
abort("bs cannot be larger than dd infile")
elsif effective_ddbsmax_bytes > ddof.total_bytes
abort("bs cannot be larger than dd outfile")
end
# Validate "iterations" parameter
iterations_array = iterations.scan(/(\D*)(\d*)(\D*)/)[0]
if iterations_array[0] != "" || iterations_array[2] != ""
abort("'" + iterations + "' is not a valid number of iterations. Must be an integer.")
else
iterations = iterations.to_i
end
# Check ddif used space is less than or equal to ddof total space
if !((ddif.total_bytes - ddif.free_bytes) <= ddof.total_bytes)
abort(ddof.path + " is too small to accommodate the contents of " + ddif.path)
end
#########
# Iterate
#########
puts "Starting tests..."
z = 0
results = Array.new()
until z == iterations
ddbs = 1
until ddbs > ddbsmax_bytes
if !system("sync")
abort("Command 'sync' failed.")
elsif !system("purge")
abort("Command 'purge' failed.")
end
count = effective_ddbsmax_bytes / ddbs
stdout = `dd if=#{ddif.path} of=#{ddof.path} bs=#{ddbs} count=#{count} 2>&1`
bytes_per_sec = stdout.scan(/\((\d*) bytes\/sec\)/)[0][0]
padding = ddbsmax_bytes_length - ddbs.to_s.length + 2
iteration_padding = iterations.to_s.length - z.to_s.length + 2
puts "Iteration: " + z.to_s + " " * iteration_padding + "bs: " + ddbs.to_s + " " * padding + "Bytes/sec: " + bytes_per_sec
results = results + [[ddbs, bytes_per_sec.to_i]]
ddbs = ddbs * 2
end
z = z + 1
end
avg_array = Array.new()
grouped = results.group_by { |s| s[0] }
# p grouped
grouped.each do |group|
avg = 0
group[1].each do |test|
avg = avg + test[1]
end
avg = avg / group[1].length.to_f
avg_array = avg_array + [[group[0], avg]]
end
conclusion_array = avg_array.sort{|x,y| y[1] <=> x[1] }[0]
puts "Optimum bs value: " + conclusion_array[0].to_s
puts "Mean transfer rate with bs=" + conclusion_array[0].to_s + ": " + conclusion_array[1].to_s + " bytes/sec"