madeleine 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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