dbfire 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/fire +260 -0
- data/lib/dbfire/filesize.rb +179 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: df593da02e1d142e3ac1f674f02bcf9d411c4f4c
|
4
|
+
data.tar.gz: 07ae4472da933e06aa32cc696d4fb1d193a047dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88ded2d6f4fc0d89da6a51937932658dbe1d08de2fcc204709e134a9c9f70fc05a3e5edc590a8081e46ba54b5ddc5bad8e7f29a1b2a984e696a6bb687388f3ea
|
7
|
+
data.tar.gz: 90acc5f7c14db69f6bfc2d2220f66df71bdd55b5655ed3701f5fda27701880cb4bf7b6f1c22b0d1f48cb857c437c3d710dc2a893e41389606896eee98de7d05a
|
data/bin/fire
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
load File.dirname(__FILE__) + '/../lib/dbfire/filesize.rb'
|
4
|
+
|
5
|
+
Thread.abort_on_exception = true
|
6
|
+
|
7
|
+
$blocksize = 65536
|
8
|
+
|
9
|
+
TYPE_PREFIXES = %w{Ki Mi Gi Ti Pi Ei Zi Yi}
|
10
|
+
|
11
|
+
class Fire
|
12
|
+
def self.usage
|
13
|
+
puts "Database load testing toolkit by m4rkw\n\n"
|
14
|
+
puts "Usage: fire <file1> [file2] [..]\n\n"
|
15
|
+
puts "To split your general.log:\n\n"
|
16
|
+
puts "fire -s <general log> <threads> [all]\n\n"
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(args)
|
21
|
+
if args[0] == '-s'
|
22
|
+
if args.length <3
|
23
|
+
self.class.usage
|
24
|
+
end
|
25
|
+
split(args)
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
@cli_string = ENV['CLI']
|
30
|
+
|
31
|
+
if !@cli_string
|
32
|
+
raise "The environment variable CLI is not set. Please set this to your cli command to invoke the database client."
|
33
|
+
end
|
34
|
+
|
35
|
+
@queue = Queue.new
|
36
|
+
|
37
|
+
@start_time = Time.now.to_i
|
38
|
+
|
39
|
+
fire(ARGV)
|
40
|
+
display
|
41
|
+
monitor
|
42
|
+
end
|
43
|
+
|
44
|
+
def split(args)
|
45
|
+
thread_id = 0
|
46
|
+
|
47
|
+
File.open(args[1],"r:ascii-8bit").each do |line|
|
48
|
+
line.chomp!
|
49
|
+
|
50
|
+
m = line.match(/[\s\t]+[0-9]+ Query[\t\s]+(\/\*.*?\*\/)?(.*)/)
|
51
|
+
|
52
|
+
if m
|
53
|
+
word = m[2].split(" ").first
|
54
|
+
|
55
|
+
if ["SELECT","INSERT","UPDATE","DELETE"].include? word
|
56
|
+
if args[3] == 'all'
|
57
|
+
target = "ALL.#{thread_id}"
|
58
|
+
else
|
59
|
+
target = "#{word}.#{thread_id}"
|
60
|
+
end
|
61
|
+
|
62
|
+
File.open(target,'a+') do |out|
|
63
|
+
out.write("#{m[2]};\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
thread_id += 1
|
67
|
+
|
68
|
+
if thread_id >= args[2].to_i
|
69
|
+
thread_id = 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fire(files)
|
77
|
+
@threads = []
|
78
|
+
@progress = []
|
79
|
+
|
80
|
+
for thread_id in 0...files.length
|
81
|
+
@progress[thread_id] = {
|
82
|
+
:total_bytes => File.size(files[thread_id]),
|
83
|
+
:sent => 0,
|
84
|
+
:sent_at_last_interval => 0,
|
85
|
+
:sent_speed => 0,
|
86
|
+
:recv => 0,
|
87
|
+
:recv_at_last_interval => 0,
|
88
|
+
:recv_speed => 0,
|
89
|
+
:timestamp => Time.now.to_i
|
90
|
+
}
|
91
|
+
|
92
|
+
thread = Thread.new(thread_id, files[thread_id]) do |tid, thread_file|
|
93
|
+
file_thread(tid, thread_file)
|
94
|
+
end
|
95
|
+
|
96
|
+
@threads.push thread
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_thread(tid, thread_file)
|
101
|
+
bytes_sent = 0
|
102
|
+
bytes_read = 0
|
103
|
+
|
104
|
+
File.open(thread_file, "r:utf-8") do |file|
|
105
|
+
IO.popen(@cli_string, "a+") do |db|
|
106
|
+
data = nil
|
107
|
+
|
108
|
+
while not file.eof?
|
109
|
+
if !data
|
110
|
+
data = file.readline
|
111
|
+
end
|
112
|
+
|
113
|
+
begin
|
114
|
+
db.write_nonblock(data)
|
115
|
+
bytes_sent += data.length
|
116
|
+
data = nil
|
117
|
+
@queue << {:thread_id => tid, :sent => bytes_sent, :recv => bytes_read}
|
118
|
+
rescue IO::WaitWritable
|
119
|
+
begin
|
120
|
+
recv = db.read_nonblock($blocksize)
|
121
|
+
bytes_read += recv.length
|
122
|
+
@queue << {:thread_id => tid, :sent => bytes_sent, :recv => bytes_read}
|
123
|
+
rescue IO::WaitReadable
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
db.close_write
|
129
|
+
|
130
|
+
while not db.eof?
|
131
|
+
begin
|
132
|
+
recv = db.read_nonblock($blocksize)
|
133
|
+
bytes_read += recv.length
|
134
|
+
@queue << {:thread_id => tid, :sent => bytes_sent, :recv => bytes_read}
|
135
|
+
rescue
|
136
|
+
IO::WaitReadable
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def monitor
|
144
|
+
Thread.new do
|
145
|
+
while 1
|
146
|
+
update
|
147
|
+
sleep 0.5
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
Thread.new do
|
152
|
+
last_update = nil
|
153
|
+
|
154
|
+
while 1
|
155
|
+
begin
|
156
|
+
value = @queue.pop
|
157
|
+
process_update(value)
|
158
|
+
rescue
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
@threads.each do |thread|
|
164
|
+
thread.join
|
165
|
+
end
|
166
|
+
|
167
|
+
execution_time = Time.at(Time.now.to_i - @start_time).utc.strftime("%H:%M:%S")
|
168
|
+
|
169
|
+
puts "\nFinished in: #{execution_time}\n\n"
|
170
|
+
end
|
171
|
+
|
172
|
+
def process_update(value)
|
173
|
+
@progress[value[:thread_id]][:sent] = value[:sent]
|
174
|
+
@progress[value[:thread_id]][:recv] = value[:recv]
|
175
|
+
|
176
|
+
time_now = Time.now.to_i
|
177
|
+
seconds_elapsed = time_now - @progress[value[:thread_id]][:timestamp]
|
178
|
+
|
179
|
+
if seconds_elapsed > 0
|
180
|
+
sent_diff = value[:sent] - @progress[value[:thread_id]][:sent_at_last_interval]
|
181
|
+
recv_diff = value[:recv] - @progress[value[:thread_id]][:recv_at_last_interval]
|
182
|
+
|
183
|
+
@progress[value[:thread_id]][:sent_speed] = sent_diff / seconds_elapsed
|
184
|
+
@progress[value[:thread_id]][:recv_speed] = recv_diff / seconds_elapsed
|
185
|
+
@progress[value[:thread_id]][:timestamp] = time_now
|
186
|
+
@progress[value[:thread_id]][:sent_at_last_interval] = value[:sent]
|
187
|
+
@progress[value[:thread_id]][:recv_at_last_interval] = value[:recv]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def display
|
192
|
+
output = []
|
193
|
+
|
194
|
+
for i in 0...@progress.length
|
195
|
+
pro = @progress[i][:sent]
|
196
|
+
total = @progress[i][:total_bytes]
|
197
|
+
pc = sprintf("%.2f", pro / (total.to_f / 100))
|
198
|
+
data_out = Filesize.from("#{@progress[i][:sent]} B").pretty
|
199
|
+
data_in = Filesize.from("#{@progress[i][:recv]} B").pretty
|
200
|
+
|
201
|
+
out_speed = Filesize.from("#{@progress[i][:sent_speed]} B").pretty + "/s"
|
202
|
+
in_speed = Filesize.from("#{@progress[i][:recv_speed]} B").pretty + "/s"
|
203
|
+
|
204
|
+
time_now = Time.now.to_i
|
205
|
+
|
206
|
+
if pro > 0 and (time_now - @start_time) >0
|
207
|
+
overall_speed = pro / (time_now - @start_time)
|
208
|
+
|
209
|
+
if overall_speed > 0
|
210
|
+
eta = (total - pro) / overall_speed
|
211
|
+
@progress[i][:eta] = Time.at(eta).utc.strftime("%H:%M:%S")
|
212
|
+
else
|
213
|
+
@progress[i][:eta] = "--"
|
214
|
+
end
|
215
|
+
else
|
216
|
+
@progress[i][:eta] = "--"
|
217
|
+
end
|
218
|
+
|
219
|
+
output.push([data_out, pc, out_speed, data_in, in_speed, @progress[i][:eta]])
|
220
|
+
end
|
221
|
+
|
222
|
+
format_output output, "%d: out: [%s] %s%% (%s) in: [%s] (%s) eta: %s"
|
223
|
+
end
|
224
|
+
|
225
|
+
def format_output(data, format)
|
226
|
+
width = []
|
227
|
+
|
228
|
+
for i in 0...data.length
|
229
|
+
for j in 0...data[i].length
|
230
|
+
if !width[j]
|
231
|
+
width[j] = data[i][j].length
|
232
|
+
elsif data[i][j].length > width[j]
|
233
|
+
width[j] = data[i][j].length
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
for i in 0...data.length
|
239
|
+
for j in 0...data[i].length
|
240
|
+
data[i][j] = data[i][j].rjust(width[j], ' ')
|
241
|
+
end
|
242
|
+
puts sprintf(format, i, *data[i])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def update
|
247
|
+
for i in 0...@progress.length
|
248
|
+
print "\e[A"
|
249
|
+
print "\e[K"
|
250
|
+
end
|
251
|
+
|
252
|
+
display
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
if ARGV.length == 0
|
257
|
+
Fire.usage
|
258
|
+
end
|
259
|
+
|
260
|
+
Fire.new(ARGV)
|
@@ -0,0 +1,179 @@
|
|
1
|
+
class Filesize
|
2
|
+
include Comparable
|
3
|
+
|
4
|
+
TYPE_PREFIXES = {
|
5
|
+
# Unit prefixes used for SI file sizes.
|
6
|
+
:SI => %w{k M G T P E Z Y},
|
7
|
+
# Unit prefixes used for binary file sizes.
|
8
|
+
:BINARY => %w{Ki Mi Gi Ti Pi Ei Zi Yi}
|
9
|
+
}
|
10
|
+
|
11
|
+
# @deprecated Please use TYPE_PREFIXES[:SI] instead
|
12
|
+
PREFIXES = TYPE_PREFIXES[:SI]
|
13
|
+
|
14
|
+
# Set of rules describing file sizes according to SI units.
|
15
|
+
SI = {
|
16
|
+
:regexp => /^([\d,.]+)?\s?([kmgtpezy]?)b$/i,
|
17
|
+
:multiplier => 1000,
|
18
|
+
:prefixes => TYPE_PREFIXES[:SI],
|
19
|
+
:presuffix => '' # deprecated
|
20
|
+
}
|
21
|
+
# Set of rules describing file sizes according to binary units.
|
22
|
+
BINARY = {
|
23
|
+
:regexp => /^([\d,.]+)?\s?(?:([kmgtpezy])i)?b$/i,
|
24
|
+
:multiplier => 1024,
|
25
|
+
:prefixes => TYPE_PREFIXES[:BINARY],
|
26
|
+
:presuffix => 'i' # deprecated
|
27
|
+
}
|
28
|
+
|
29
|
+
# @param [Number] size A file size, in bytes.
|
30
|
+
# @param [SI, BINARY] type Which type to use for conversions.
|
31
|
+
def initialize(size, type = BINARY)
|
32
|
+
@bytes = size.to_i
|
33
|
+
@type = type
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Number] Returns the size in bytes.
|
37
|
+
def to_i
|
38
|
+
@bytes
|
39
|
+
end
|
40
|
+
alias_method :to_int, :to_i
|
41
|
+
|
42
|
+
# @param [String] unit Which unit to convert to.
|
43
|
+
# @return [Float] Returns the size in a given unit.
|
44
|
+
def to(unit = 'B')
|
45
|
+
to_parts = self.class.parse(unit)
|
46
|
+
prefix = to_parts[:prefix]
|
47
|
+
|
48
|
+
if prefix == 'B' or prefix.empty?
|
49
|
+
return to_i.to_f
|
50
|
+
end
|
51
|
+
|
52
|
+
to_type = to_parts[:type]
|
53
|
+
size = @bytes
|
54
|
+
|
55
|
+
pos = (@type[:prefixes].map { |s| s[0].chr.downcase }.index(prefix.downcase) || -1) + 1
|
56
|
+
|
57
|
+
size = size/(to_type[:multiplier].to_f**(pos)) unless pos < 1
|
58
|
+
end
|
59
|
+
alias_method :to_f, :to
|
60
|
+
|
61
|
+
# @param (see #to_f)
|
62
|
+
# @return [String] Same as {#to_f}, but as a string, with the unit appended.
|
63
|
+
# @see #to_f
|
64
|
+
def to_s(unit = 'B')
|
65
|
+
"%.2f %s" % [to(unit).to_f.to_s, unit]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Same as {#to_s} but with an automatic determination of the most
|
69
|
+
# sensible unit.
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
# @see #to_s
|
73
|
+
def pretty
|
74
|
+
size = @bytes
|
75
|
+
if size < @type[:multiplier]
|
76
|
+
unit = "B"
|
77
|
+
else
|
78
|
+
pos = (Math.log(size) / Math.log(@type[:multiplier])).floor
|
79
|
+
pos = @type[:prefixes].size-1 if pos > @type[:prefixes].size - 1
|
80
|
+
|
81
|
+
unit = @type[:prefixes][pos-1] + "B"
|
82
|
+
end
|
83
|
+
|
84
|
+
to_s(unit)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Filesize]
|
88
|
+
def +(other)
|
89
|
+
self.class.new(@bytes + other.to_i, @type)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Filesize]
|
93
|
+
def -(other)
|
94
|
+
self.class.new(@bytes - other.to_i, @type)
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Filesize]
|
98
|
+
def *(other)
|
99
|
+
self.class.new(@bytes * other.to_i, @type)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Filesize]
|
103
|
+
def /(other)
|
104
|
+
result = @bytes / other.to_f
|
105
|
+
if other.is_a? Filesize
|
106
|
+
result
|
107
|
+
else
|
108
|
+
self.class.new(result, @type)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def <=>(other)
|
113
|
+
self.to_i <=> other.to_i
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Array<self, other>]
|
117
|
+
# @api private
|
118
|
+
def coerce(other)
|
119
|
+
return self, other
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
# Parses a string, which describes a file size, and returns a
|
124
|
+
# Filesize object.
|
125
|
+
#
|
126
|
+
# @param [String] arg A file size to parse.
|
127
|
+
# @raise [ArgumentError] Raised if the file size cannot be parsed properly.
|
128
|
+
# @return [Filesize]
|
129
|
+
def from(arg)
|
130
|
+
parts = parse(arg)
|
131
|
+
prefix = parts[:prefix]
|
132
|
+
size = parts[:size]
|
133
|
+
type = parts[:type]
|
134
|
+
|
135
|
+
raise ArgumentError, "Unparseable filesize" unless type
|
136
|
+
|
137
|
+
offset = (type[:prefixes].map { |s| s[0].chr.downcase }.index(prefix.downcase) || -1) + 1
|
138
|
+
|
139
|
+
new(size * (type[:multiplier] ** (offset)), type)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Hash<:prefix, :size, :type>]
|
143
|
+
# @api private
|
144
|
+
def parse(string)
|
145
|
+
type = nil
|
146
|
+
# in this order, so we prefer binary :)
|
147
|
+
[BINARY, SI].each { |_type|
|
148
|
+
if string =~ _type[:regexp]
|
149
|
+
type = _type
|
150
|
+
break
|
151
|
+
end
|
152
|
+
}
|
153
|
+
|
154
|
+
prefix = $2 || ''
|
155
|
+
size = ($1 || 0).to_f
|
156
|
+
|
157
|
+
return { :prefix => prefix, :size => size, :type => type}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# The size of a floppy disk
|
162
|
+
Floppy = Filesize.from("1474 KiB")
|
163
|
+
# The size of a CD
|
164
|
+
CD = Filesize.from("700 MB")
|
165
|
+
# The size of a common DVD
|
166
|
+
DVD_5 = Filesize.from("4.38 GiB")
|
167
|
+
# The same as a DVD 5
|
168
|
+
DVD = DVD_5
|
169
|
+
# The size of a single-sided dual-layer DVD
|
170
|
+
DVD_9 = Filesize.from("7.92 GiB")
|
171
|
+
# The size of a double-sided single-layer DVD
|
172
|
+
DVD_10 = DVD_5 * 2
|
173
|
+
# The size of a double-sided DVD, combining a DVD-9 and a DVD-5
|
174
|
+
DVD_14 = DVD_9 + DVD_5
|
175
|
+
# The size of a double-sided dual-layer DVD
|
176
|
+
DVD_18 = DVD_14 * 2
|
177
|
+
# The size of a Zip disk
|
178
|
+
ZIP = Filesize.from("100 MB")
|
179
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dbfire
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- m4rkw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-29 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Simple, fast, standalone database load testing toolkid
|
14
|
+
email: m@rkw.io
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- bin/fire
|
20
|
+
- lib/dbfire/filesize.rb
|
21
|
+
homepage: https://github.com/m4rkw/dbfire
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.4.5.1
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: DBFire
|
45
|
+
test_files: []
|