madeleine 0.7.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.
@@ -0,0 +1,35 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ $LOAD_PATH.unshift("../lib")
4
+
5
+ require 'madeleine'
6
+ require 'batched'
7
+
8
+ class BenchmarkCommand
9
+ def initialize(value)
10
+ @value = value
11
+ end
12
+
13
+ def execute(system)
14
+ # do nothing
15
+ end
16
+ end
17
+
18
+ madeleine = BatchedSnapshotMadeleine.new("benchmark-base") { :the_system }
19
+
20
+ RUNS = 2000
21
+
22
+ GC.start
23
+ GC.disable
24
+
25
+ t0 = Time.now
26
+ RUNS.times {
27
+ madeleine.execute_command(BenchmarkCommand.new(1234))
28
+ }
29
+ t1 = Time.now
30
+
31
+ GC.enable
32
+
33
+ tps = RUNS/(t1 - t0)
34
+
35
+ puts "#{tps.to_i} transactions/s"
@@ -0,0 +1,245 @@
1
+ #!/usr/local/bin/ruby -w
2
+ #
3
+ # Copyright(c) 2003 H�kan R�berg
4
+ #
5
+ # Some components taken from test_persistence.rb
6
+ # Copyright(c) 2003 Anders Bengtsson
7
+ #
8
+
9
+ $LOAD_PATH.unshift("../lib")
10
+
11
+ require 'batched'
12
+ require 'test/unit'
13
+ require 'madeleine/clock'
14
+
15
+
16
+ module Madeleine::Batch
17
+ class BatchedSnapshotMadeleineTest < Test::Unit::TestCase
18
+
19
+ class ArraySystem < Array
20
+ include Madeleine::Clock::ClockedSystem
21
+ end
22
+
23
+ class PushCommand
24
+ def initialize(value)
25
+ @value = value
26
+ end
27
+
28
+ def execute(system)
29
+ system << @value
30
+ end
31
+ end
32
+
33
+ class ArrayQuery
34
+ def initialize
35
+ @time = []
36
+ end
37
+
38
+ def execute(system)
39
+ length = system.length
40
+ @time << system.clock.time
41
+
42
+ a = 1
43
+ system.each do |n|
44
+ a *= n
45
+ end
46
+
47
+ raise "inconsistent read" unless length == system.length
48
+ raise "inconsistent read" unless @time.last == system.clock.time
49
+ end
50
+ end
51
+
52
+ def test_live_snapshot
53
+ system = ArraySystem.new
54
+ w, r = [], []
55
+ going = true
56
+
57
+ madeleine = BatchedSnapshotMadeleine.new(prevalence_base) { system }
58
+
59
+ i = 0
60
+ 10.times do |n|
61
+ w[n] = Thread.new {
62
+ while going
63
+ madeleine.execute_command(PushCommand.new(i))
64
+ i += 1
65
+ sleep(0.1)
66
+ end
67
+ }
68
+ end
69
+
70
+ q = 0
71
+ query = ArrayQuery.new
72
+ 100.times do |n|
73
+ r[n] = Thread.new {
74
+ while going
75
+ begin
76
+ madeleine.execute_query(query)
77
+ q += 1
78
+ rescue
79
+ fail("Query blocks writing")
80
+ end
81
+ sleep(0.1)
82
+ end
83
+ }
84
+ end
85
+
86
+ s = 0
87
+ snapshot = Thread.new {
88
+ while going
89
+ madeleine.take_snapshot
90
+ s += 1
91
+ sleep(0.01)
92
+ end
93
+ }
94
+
95
+ sleep(1)
96
+
97
+ going = false
98
+
99
+ r.each do |t|
100
+ t.join
101
+ end
102
+
103
+ w.each do |t|
104
+ t.join
105
+ end
106
+
107
+ snapshot.join
108
+
109
+ madeleine.close
110
+
111
+ madeleine2 = SnapshotMadeleine.new(prevalence_base)
112
+ assert_equal(madeleine.system, madeleine2.system, "Take system snapshots while accessing")
113
+ end
114
+
115
+ def prevalence_base
116
+ "BatchedSnapshot"
117
+ end
118
+
119
+ def teardown
120
+ delete_directory(prevalence_base)
121
+ end
122
+ end
123
+
124
+ class BatchedLogTest < Test::Unit::TestCase
125
+
126
+ class MockMadeleine
127
+ def initialize(logger)
128
+ @logger = logger
129
+ end
130
+
131
+ def flush
132
+ @logger.flush
133
+ end
134
+ end
135
+
136
+ class MockCommand
137
+ attr_reader :text
138
+
139
+ def initialize(text)
140
+ @text = text
141
+ end
142
+
143
+ def execute(system)
144
+ end
145
+
146
+ def ==(o)
147
+ o.text == @text
148
+ end
149
+ end
150
+
151
+ module BufferInspector
152
+ def buffer_size
153
+ @buffer.size
154
+ end
155
+ end
156
+
157
+ def setup
158
+ @target = BatchedLogger.new(".", BatchedLogFactory.new, nil)
159
+ @target.extend(BufferInspector)
160
+ @madeleine = MockMadeleine.new(@target)
161
+ @messages = []
162
+ end
163
+
164
+ def test_logging
165
+ actor = LogActor.launch(@madeleine, 0.1)
166
+
167
+ append("Hello")
168
+ sleep(0.01)
169
+ append("World")
170
+ sleep(0.01)
171
+
172
+ assert_equal(2, @target.buffer_size, "Batched command queue")
173
+ assert(!File.exist?(expected_file_name), "Batched commands not on disk")
174
+
175
+ sleep(0.2)
176
+
177
+ assert_equal(0, @target.buffer_size, "Queue emptied by batched write")
178
+ file_size = File.size(expected_file_name)
179
+ assert(file_size > 0, "Queue written to disk")
180
+
181
+ append("Again")
182
+ sleep(0.2)
183
+
184
+ assert(File.size(expected_file_name) > file_size, "Command written to disk")
185
+
186
+ f = File.new(expected_file_name)
187
+
188
+ @messages.each do |message|
189
+ assert_equal(message, Marshal.load(f), "Commands logged in order")
190
+ end
191
+
192
+ f.close
193
+
194
+ actor.destroy
195
+ @target.flush
196
+ @target.close
197
+
198
+ end
199
+
200
+ def append(text)
201
+ Thread.new {
202
+ message = MockCommand.new(text)
203
+ @messages << message
204
+ queued_command = QueuedCommand.new(message)
205
+ @target.store(queued_command)
206
+ queued_command.wait_for
207
+ }
208
+ end
209
+
210
+ def expected_file_name
211
+ "000000000000000000001.command_log"
212
+ end
213
+
214
+ def teardown
215
+ assert(File.delete(expected_file_name) == 1)
216
+ end
217
+
218
+ end
219
+
220
+ def delete_directory(directory_name)
221
+ Dir.foreach(directory_name) do |file|
222
+ next if file == "."
223
+ next if file == ".."
224
+ assert(File.delete(directory_name + File::SEPARATOR + file) == 1,
225
+ "Unable to delete #{file}")
226
+ end
227
+ Dir.delete(directory_name)
228
+ end
229
+ end
230
+
231
+ include Madeleine::Batch
232
+
233
+ def add_batched_tests(suite)
234
+ suite << BatchedSnapshotMadeleineTest.suite
235
+ suite << BatchedLogTest.suite
236
+ end
237
+
238
+ if __FILE__ == $0
239
+ suite = Test::Unit::TestSuite.new("BatchedLogTest")
240
+ add_batched_tests(suite)
241
+
242
+ require 'test/unit/ui/console/testrunner'
243
+ Thread.abort_on_exception = true
244
+ Test::Unit::UI::Console::TestRunner.run(suite)
245
+ end
@@ -0,0 +1,248 @@
1
+ #!/usr/local/bin/ruby -w
2
+ #
3
+ # Copyright(c) 2003 H�kan R�berg
4
+ #
5
+ # This test is based on Prevaylers TransactionTestRun,
6
+ # Copyright(c) 2001-2003 Klaus Wuestefeld.
7
+ #
8
+
9
+ $LOAD_PATH.unshift("../lib")
10
+
11
+ require 'madeleine'
12
+ require 'madeleine/clock'
13
+ require 'batched'
14
+
15
+ module ScalabilityTest
16
+
17
+ class TransactionTestRun
18
+ MIN_THREADS = 20
19
+ MAX_THREADS = 20
20
+ NUMBER_OF_OBJECTS = 100000
21
+ ROUND_DURATION = 20
22
+ DIR = "ScalabilityBase"
23
+
24
+ def initialize
25
+ @system = TransactionSystem.new
26
+ @madeleine = BatchedSnapshotMadeleine.new(DIR) { @system }
27
+
28
+ @system.replace_all_records(create_records(NUMBER_OF_OBJECTS))
29
+
30
+ @is_round_finished = false
31
+
32
+ @best_round_ops_per_s = 0
33
+ @best_round_threads = 0
34
+ @operation_count = 0
35
+ @last_operation = 0
36
+ @active_round_threads = 0
37
+
38
+ @half_of_the_objects = NUMBER_OF_OBJECTS / 2
39
+
40
+ @connection_cache = []
41
+ @connection_cache_lock = Mutex.new
42
+
43
+ ObjectSpace.garbage_collect
44
+
45
+ puts "========= Running " + name + " (" + (MAX_THREADS - MIN_THREADS + 1).to_s + " rounds). Subject: " + subject_name + "..."
46
+ puts "Each round will take approx. " + ROUND_DURATION.to_s + " seconds to run..."
47
+ perform_test
48
+ puts "----------- BEST ROUND: " + result_string(@best_round_ops_per_s, @best_round_threads)
49
+
50
+ @madeleine.close
51
+ delete_directory(DIR)
52
+ end
53
+
54
+ def name
55
+ "Transaction Test"
56
+ end
57
+
58
+ def subject_name
59
+ "Madeleine"
60
+ end
61
+
62
+ def result_string(ops_per_s, threads)
63
+ ops_per_s.to_s + " operations/second (" + threads.to_s + " threads)"
64
+ end
65
+
66
+ def perform_test
67
+ for threads in MIN_THREADS..MAX_THREADS
68
+ ops_per_s = perform_round(threads)
69
+ if ops_per_s > @best_round_ops_per_s
70
+ @best_round_ops_per_s = ops_per_s
71
+ @best_round_threads = threads
72
+ end
73
+ end
74
+ end
75
+
76
+ def perform_round(threads)
77
+ initial_operation_count = @operation_count
78
+ start_time = Time.now.to_f
79
+
80
+ start_threads(threads)
81
+ sleep(ROUND_DURATION)
82
+ stop_threads
83
+
84
+ seconds_ellapsed = Time.now.to_f - start_time
85
+ ops_per_second = (@operation_count - initial_operation_count) / seconds_ellapsed
86
+
87
+ puts
88
+ puts "Seconds ellapsed: " + seconds_ellapsed.to_s
89
+ puts "--------- Round Result: " + result_string(ops_per_second, threads)
90
+
91
+ ops_per_second
92
+ end
93
+
94
+ def start_threads(threads)
95
+ @is_round_finished = false
96
+ for i in 1..threads
97
+ start_thread(@last_operation + i, threads)
98
+ end
99
+ end
100
+
101
+ def start_thread(starting_operation, operation_increment)
102
+ Thread.new {
103
+ connection = accquire_connection
104
+
105
+ operation = starting_operation
106
+ while not @is_round_finished
107
+ # puts "Operation " + operation.to_s
108
+ execute_operation(connection, operation)
109
+ operation += operation_increment
110
+ end
111
+
112
+ @connection_cache_lock.synchronize do
113
+ @connection_cache << connection
114
+ @operation_count += (operation - starting_operation) / operation_increment
115
+ @last_operation = operation if @last_operation < operation
116
+ @active_round_threads -= 1
117
+ end
118
+ }
119
+ @active_round_threads += 1
120
+ end
121
+
122
+ def execute_operation(connection, operation)
123
+ record_to_insert = Record.new(NUMBER_OF_OBJECTS + operation)
124
+ id_to_delete = spread_id(operation)
125
+ record_to_update = Record.new(@half_of_the_objects + id_to_delete)
126
+
127
+ connection.perform_transaction(record_to_insert, record_to_update, id_to_delete)
128
+ end
129
+
130
+ def spread_id(id)
131
+ (id / @half_of_the_objects) * @half_of_the_objects + ((id * 16807) % @half_of_the_objects)
132
+ end
133
+
134
+ def create_test_connection
135
+ TransactionConnection.new(@madeleine)
136
+ end
137
+
138
+ def accquire_connection
139
+ @connection_cache_lock.synchronize do
140
+ return @connection_cache.empty? ? create_test_connection : @connection_cache.shift
141
+ end
142
+ end
143
+
144
+ def stop_threads
145
+ @is_round_finished = true
146
+ while @active_round_threads != 0
147
+ sleep(0.001)
148
+ end
149
+ end
150
+
151
+ def create_records(number_of_objects)
152
+ result = []
153
+ for i in 0..number_of_objects
154
+ result << Record.new(i)
155
+ end
156
+ result
157
+ end
158
+
159
+
160
+ def delete_directory(directory_name)
161
+ Dir.foreach(directory_name) do |file|
162
+ next if file == "."
163
+ next if file == ".."
164
+ File.delete(directory_name + File::SEPARATOR + file)
165
+ end
166
+ Dir.delete(directory_name)
167
+ end
168
+ end
169
+
170
+ class TransactionSystem
171
+ include Madeleine::Clock::ClockedSystem
172
+
173
+ def initialize
174
+ @records_by_id = Hash.new
175
+ @transaction_lock = Mutex.new
176
+ end
177
+
178
+ def perform_transaction(record_to_insert, record_to_update, id_to_delete)
179
+ @transaction_lock.synchronize do
180
+ put(record_to_insert)
181
+ put(record_to_update)
182
+ @records_by_id.delete(id_to_delete)
183
+ end
184
+ end
185
+
186
+ def put(new_record)
187
+ @records_by_id[new_record.id] = new_record
188
+ end
189
+
190
+ def replace_all_records(new_records)
191
+ @records_by_id.clear
192
+ new_records.each do |record|
193
+ put(record)
194
+ end
195
+ end
196
+ end
197
+
198
+ class TransactionConnection
199
+ def initialize(madeleine)
200
+ @madeleine = madeleine
201
+ end
202
+
203
+ def perform_transaction(record_to_insert, record_to_update, id_to_delete)
204
+ @madeleine.execute_command(TestTransaction.new(record_to_insert, record_to_update, id_to_delete))
205
+ end
206
+ end
207
+
208
+ class TestTransaction
209
+ def initialize(record_to_insert, record_to_update, id_to_delete)
210
+ @record_to_insert = record_to_insert
211
+ @record_to_update = record_to_update
212
+ @id_to_delete = id_to_delete
213
+ end
214
+
215
+ def execute(system)
216
+ system.perform_transaction(@record_to_insert, @record_to_update, @id_to_delete)
217
+ end
218
+ end
219
+
220
+ class Record
221
+ attr_reader :id, :name, :string_1, :date_1, :date_2
222
+
223
+ def initialize(id)
224
+ @id = id
225
+ @name = "NAME" + (id % 10000).to_s
226
+ @string_1 = (id % 10000).to_s == 0 ? Record.large_string + id : nil;
227
+ @date_1 = Record.random_date
228
+ @date_2 = Record.random_date
229
+ end
230
+
231
+ def self.large_string
232
+ [].fill("A", 1..980).to_s
233
+ end
234
+
235
+ def self.random_date
236
+ rand(10000000)
237
+ end
238
+ end
239
+ end
240
+
241
+ if __FILE__ == $0
242
+ puts "Madeleine Scalability Test"
243
+ puts "Based on Prevaylers Scalability Test"
244
+ puts
245
+
246
+ Thread.abort_on_exception = true
247
+ ScalabilityTest::TransactionTestRun.new
248
+ end