mrd 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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +1 -0
- data/bin/mrd +259 -0
- data/lib/mrd/version.rb +3 -0
- data/lib/mrd.rb +5 -0
- data/mrd.gemspec +22 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Julien Letessier
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# mrd
|
2
|
+
|
3
|
+
> Stands for MySQL RAM Disk
|
4
|
+
|
5
|
+
Spawns a temporary MySQL instance off a RAM disk. Useful to speed up large test suites, your machine usable while running them, and preserve your SSD's life!
|
6
|
+
|
7
|
+
Only works under Mac OS X.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
$ gem install mrd
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
$ mrd
|
16
|
+
==>
|
17
|
+
Created Ramdisk at /dev/disk4
|
18
|
+
Formatted Ramdisk at /dev/disk4
|
19
|
+
Mounted Ramdisk at /Volumes/MySQLRAMDisk
|
20
|
+
Starting MySQL server
|
21
|
+
MySQL is now running.
|
22
|
+
Configure you client to use the root user, no password, and the socket at '/Volumes/MySQLRAMDisk/mysql.sock'.
|
23
|
+
Just close this terminal or press ^C when you no longer need it.
|
24
|
+
|
25
|
+
Then, if using Rails, point your `database.yml` to the temporary SQL server:
|
26
|
+
|
27
|
+
test:
|
28
|
+
...
|
29
|
+
socket: /Volumes/MySQLRAMDisk/mysql.sock
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mrd
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'term/ansicolor'
|
8
|
+
|
9
|
+
class App
|
10
|
+
class ColorLogger < ::Logger
|
11
|
+
Colors = {
|
12
|
+
'DEBUG' => :reset,
|
13
|
+
'INFO' => :green,
|
14
|
+
'WARN' => :yellow,
|
15
|
+
'ERROR' => :red,
|
16
|
+
'FATAL' => :red,
|
17
|
+
'UNKNOWN' => :red
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
self.formatter = self.method(:custom_formatter)
|
23
|
+
end
|
24
|
+
|
25
|
+
def custom_formatter(severity, time, progname, msg)
|
26
|
+
Term::ANSIColor.send(Colors[severity], "#{msg}\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
Logger = ColorLogger.new(STDOUT).tap do |logger|
|
32
|
+
logger.level = ColorLogger::INFO
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
module Runner
|
37
|
+
CommandError = Class.new(Exception)
|
38
|
+
|
39
|
+
def run(command)
|
40
|
+
Logger.debug("Running '#{command}'")
|
41
|
+
return %x(#{command}).strip
|
42
|
+
end
|
43
|
+
|
44
|
+
def run!(command)
|
45
|
+
run(command).tap do |result|
|
46
|
+
raise CommandError.new($?) if $? != 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class Ramdisk
|
53
|
+
include Runner
|
54
|
+
|
55
|
+
def initialize(size_in_megabytes)
|
56
|
+
@size_in_megabytes = size_in_megabytes
|
57
|
+
@mountpoint = nil
|
58
|
+
@device = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def mount
|
62
|
+
@device = run!("hdid -nomount ram://#{sectors}")
|
63
|
+
Logger.info("Created Ramdisk at #{@device}")
|
64
|
+
|
65
|
+
run!("diskutil erasevolume HFS+ MySQLRAMDisk #{@device}")
|
66
|
+
Logger.info("Formatted Ramdisk at #{@device}")
|
67
|
+
Logger.info("Mounted Ramdisk at #{mountpoint}")
|
68
|
+
self
|
69
|
+
rescue Exception => e
|
70
|
+
Logger.error("Mounting failed: #{e.class.name} #{e.message}")
|
71
|
+
unmount
|
72
|
+
end
|
73
|
+
|
74
|
+
def unmount
|
75
|
+
return unless @device
|
76
|
+
Logger.info 'Ejecting RAM disk'
|
77
|
+
run!("hdiutil eject #{@device}")
|
78
|
+
@mountpoint = nil
|
79
|
+
self
|
80
|
+
rescue CommandError
|
81
|
+
run("hdiutil eject -force #{@device}")
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def mountpoint
|
87
|
+
@mountpoint ||= begin
|
88
|
+
mounts = run!("mount")
|
89
|
+
mounts =~ /#{@device} on (.*) \(/
|
90
|
+
$1 or raise RuntimeError.new("Cannot determine mountpoint")
|
91
|
+
Pathname.new($1)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def sectors
|
98
|
+
@size_in_megabytes * 1024*1024/512
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
class MySQLServer
|
104
|
+
include Runner
|
105
|
+
|
106
|
+
attr :socket
|
107
|
+
|
108
|
+
def initialize(ramdisk)
|
109
|
+
@ramdisk = ramdisk
|
110
|
+
@socket = @ramdisk.mountpoint.join('mysql.sock')
|
111
|
+
@pidfile = @ramdisk.mountpoint.join('mysql.pid')
|
112
|
+
@config = @ramdisk.mountpoint.join('my.cnf')
|
113
|
+
|
114
|
+
@install_db = find_command! %w(mysql_install_db5 mysql_install_db)
|
115
|
+
@mysqld = find_command! %w(mysqld_safe5 mysqld_safe)
|
116
|
+
end
|
117
|
+
|
118
|
+
def start
|
119
|
+
# Setup config
|
120
|
+
File.open(@config, 'w') do |fd|
|
121
|
+
fd.write %Q{
|
122
|
+
[client]
|
123
|
+
socket = #{@socket}
|
124
|
+
[mysqld]
|
125
|
+
socket = #{@socket}
|
126
|
+
thread_concurrency = 4
|
127
|
+
# innodb_data_home_dir =
|
128
|
+
innodb_data_file_path = ibdata1:10M:autoextend
|
129
|
+
#innodb_log_group_home_dir = /opt/local/var/db/mysql5
|
130
|
+
innodb_file_per_table
|
131
|
+
ft_min_word_len = 3
|
132
|
+
}.gsub(/^\s+/,'')
|
133
|
+
end
|
134
|
+
|
135
|
+
# Setup base databases
|
136
|
+
run!("#{@install_db} --datadir='#{@ramdisk.mountpoint}'")
|
137
|
+
|
138
|
+
# Start server in another thread
|
139
|
+
Logger.info 'Starting MySQL server'
|
140
|
+
@thread = Thread.new do
|
141
|
+
run!("#{@mysqld} --defaults-file='#{@config}' --socket='#{@socket}' --datadir='#{@ramdisk.mountpoint}' --pid-file='#{@pidfile}' --skip-networking")
|
142
|
+
end
|
143
|
+
|
144
|
+
# Wait for PID file
|
145
|
+
wait_for { Logger.debug "Waiting for PID file..." ; @pidfile.exist? }
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
def stop
|
150
|
+
return unless @pidfile
|
151
|
+
# Terminate server
|
152
|
+
Logger.info "Stopping server..."
|
153
|
+
pid = @pidfile.read.to_i
|
154
|
+
Process.kill("TERM", pid)
|
155
|
+
Logger.debug "Killed server process (#{pid})."
|
156
|
+
wait_for { Logger.debug "Waiting for PID file..." ; !@pidfile.exist? }
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def wait_for(options = {})
|
162
|
+
tries = options.fetch(:tries, 10)
|
163
|
+
delay = options.fetch(:delay, 1.0)
|
164
|
+
1.upto(tries) do |index|
|
165
|
+
break if yield
|
166
|
+
Kernel.sleep(delay)
|
167
|
+
end
|
168
|
+
yield
|
169
|
+
end
|
170
|
+
|
171
|
+
def find_command!(variants)
|
172
|
+
variants.each do |variant|
|
173
|
+
path = run("which #{variant}")
|
174
|
+
return path unless path.empty?
|
175
|
+
end
|
176
|
+
Logger.error "Sorry, I need one of #{variants.join ', '} to run."
|
177
|
+
Kernel.exit
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def initialize(argv)
|
183
|
+
@options = OpenStruct.new(:verbose => false, :size => 1024)
|
184
|
+
option_parser.parse(argv)
|
185
|
+
|
186
|
+
Logger.level = options.verbose ?
|
187
|
+
ColorLogger::DEBUG :
|
188
|
+
ColorLogger::INFO
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def main
|
193
|
+
if pid = Process.fork
|
194
|
+
parent_main(pid)
|
195
|
+
else
|
196
|
+
child_main
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def parent_main(child_pid)
|
201
|
+
# Logger.info 'in parent'
|
202
|
+
Process.detach(child_pid)
|
203
|
+
wait_forever
|
204
|
+
rescue Interrupt
|
205
|
+
Kernel.exit
|
206
|
+
end
|
207
|
+
|
208
|
+
def child_main
|
209
|
+
# Logger.info 'in child'
|
210
|
+
@ramdisk = Ramdisk.new(options.size).mount
|
211
|
+
@server = MySQLServer.new(@ramdisk).start
|
212
|
+
Signal.trap("HUP") do
|
213
|
+
Logger.warn "SIGHUP received"
|
214
|
+
cleanup
|
215
|
+
end
|
216
|
+
|
217
|
+
# Log and wait for user input
|
218
|
+
Logger.info "MySQL is now running."
|
219
|
+
Logger.info "Configure you client to use the root user, no password, and the socket at '#{@server.socket}'."
|
220
|
+
Logger.info "Just close this terminal or press ^C when you no longer need it."
|
221
|
+
wait_forever
|
222
|
+
|
223
|
+
rescue Interrupt, SystemExit
|
224
|
+
Logger.warn 'Session terminated.'
|
225
|
+
ensure
|
226
|
+
cleanup
|
227
|
+
end
|
228
|
+
|
229
|
+
def cleanup
|
230
|
+
@server.stop if @server
|
231
|
+
@ramdisk.unmount if @ramdisk
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
attr :options
|
238
|
+
|
239
|
+
def option_parser
|
240
|
+
OptionParser.new do |opts|
|
241
|
+
opts.banner = "Usage: #{$progname} [options]"
|
242
|
+
|
243
|
+
opts.on("-v", "--verbose", "Run verbosely") do |v|
|
244
|
+
options.verbose = true
|
245
|
+
end
|
246
|
+
|
247
|
+
opts.on("-s", "--size SIZE", Integer, "Size of RAM disk in MB (default 1024)") do |v|
|
248
|
+
options.size = v
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def wait_forever
|
254
|
+
Kernel.sleep 1_000_000
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
App.new(ARGV).main
|
data/lib/mrd/version.rb
ADDED
data/lib/mrd.rb
ADDED
data/mrd.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mrd/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "mrd"
|
8
|
+
gem.version = Mrd::VERSION
|
9
|
+
gem.authors = ["Julien Letessier"]
|
10
|
+
gem.email = ["julien.letessier@gmail.com"]
|
11
|
+
gem.description = %q{Under Darwin, spawns a temporary MySQL instance off a RAM disk. Useful to speed up large test suites, your machine usable while running them, and preserve your SSD's life!}
|
12
|
+
gem.summary = %q{Run MySQL off a RAM disk}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.add_development_dependency "rake"
|
16
|
+
gem.add_dependency "term-ansicolor"
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($/)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.require_paths = ["lib"]
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mrd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Julien Letessier
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: term-ansicolor
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Under Darwin, spawns a temporary MySQL instance off a RAM disk. Useful
|
47
|
+
to speed up large test suites, your machine usable while running them, and preserve
|
48
|
+
your SSD's life!
|
49
|
+
email:
|
50
|
+
- julien.letessier@gmail.com
|
51
|
+
executables:
|
52
|
+
- mrd
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- Gemfile
|
58
|
+
- LICENSE.txt
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- bin/mrd
|
62
|
+
- lib/mrd.rb
|
63
|
+
- lib/mrd/version.rb
|
64
|
+
- mrd.gemspec
|
65
|
+
homepage: ''
|
66
|
+
licenses: []
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
hash: -335233365963778714
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
hash: -335233365963778714
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.23
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Run MySQL off a RAM disk
|
95
|
+
test_files: []
|