gravitext-util 1.4.0-java

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/History.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ === 1.4.0 (2010-5-8)
2
+ * New generic UniMap (Java) with ruby dynamic accessor extension.
3
+ * New Charsets constants.
4
+ * New Streams utilities; ByteBufferInputStream and ByteArrayInputStream.
5
+ * New ResizableByteBufferOutputStream.
6
+ * New Closeables utilities.
7
+ * ArrayHTMap and HashHTMap are no longer final (supports UniMap).
8
+ * Added immutable public KeySpace.keys().
9
+ * Added convenience methods KeySpace.createGeneric() and createListKey().
10
+ * Added URL64 encoder/decoder utility.
11
+ * Added explicit dev dependencies for rjack-slf4j and rjack-logback.
12
+ * Remove old/deprecated perftest.* harness and ConcurrentTest*
13
+ * Use rjack-tarpit for build.
14
+ * Set gem platform to java.
15
+
16
+ === 1.3.2 (2009-8-6)
17
+ * Add ConstrainedInputStream.readLength() and mark/reset support.
18
+
19
+ === 1.3.1 (2009-8-2)
20
+ * ResizableByteBuffer.putFromStream break on zero length read.
21
+ * Add ConstrainedInputStream.
22
+ * Use rdoc 2.4.3 and hoe 1.12.2 for build.
23
+
24
+ === 1.3 (2009-2-23)
25
+ * New gem packaging.
26
+ * Replaced java perftest harness with ruby test harness/wiring.
27
+ * Split heterogeneous access methods from HTMap to HTAccess interface
28
+ to enable generic filters and other consumers.
29
+ * Non-checked exception version of Closeable interface.
30
+
31
+ === 1.2 (2008-4-28)
32
+ * Added ResizableByteBuffer, ResizableCharBuffer,
33
+ ResizableCharBufferWriter classes.
34
+
35
+ === 1.1 (2007-7-15)
36
+ * First public release.
data/Manifest.txt ADDED
@@ -0,0 +1,16 @@
1
+ History.rdoc
2
+ Manifest.txt
3
+ NOTICE.txt
4
+ README.rdoc
5
+ Rakefile
6
+ pom.xml
7
+ bin/gravitext-perftest
8
+ lib/gravitext-util/version.rb
9
+ lib/gravitext-util.rb
10
+ lib/gravitext-util/concurrent.rb
11
+ lib/gravitext-util/perftest.rb
12
+ lib/gravitext-util/unimap.rb
13
+ test/test_concurrent.rb
14
+ test/test_perftest.rb
15
+ test/test_unimap.rb
16
+ lib/gravitext-util/gravitext-util-1.4.0.jar
data/NOTICE.txt ADDED
@@ -0,0 +1,2 @@
1
+ gravitext-util
2
+ Copyright (c) 2007-2010 David Kellum
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = gravitext-util
2
+
3
+ * http://gravitext.rubyforge.org
4
+ * http://gravitext.com/oss/gravitext-util
5
+ * http://github.com/dekellum/gravitext
6
+
7
+ == Description
8
+
9
+ A collection of core java utilities with ruby adapters for JRuby.
10
+
11
+ * A concurrent (thread safety) testing facility for java JUnit,
12
+ ruby Test::Unit, or other test harnesses.
13
+
14
+ * A concurrent performance testing facility for java/JRuby with simple
15
+ test wiring in ruby.
16
+
17
+ * A Heterogeneous Type-safe Map implementation in java with
18
+ dynamically generated ruby accessors.
19
+
20
+ * A set of core java utility classes (resizable buffers, a stopwatch,
21
+ SI unit formatting, fast random number generator), in support of the
22
+ above and of general utility.
23
+
24
+ == Dependencies
25
+
26
+ * Java 1.5+
27
+ * JRuby 1.1.6+
28
+ * rjack-slf4j[http://rjack.rubyforge.org/slf4j] and
29
+ rjack-logback[http://rjack.rubyforge.org/logback] gems for testing.
30
+
31
+ == License
32
+
33
+ Copyright (c) 2007-2010 David Kellum
34
+
35
+ Licensed under the Apache License, Version 2.0 (the "License"); you
36
+ may not use this file except in compliance with the License. You
37
+ may obtain a copy of the License at:
38
+
39
+ http://www.apache.org/licenses/LICENSE-2.0
40
+
41
+ Unless required by applicable law or agreed to in writing, software
42
+ distributed under the License is distributed on an "AS IS" BASIS,
43
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
44
+ implied. See the License for the specific language governing
45
+ permissions and limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # -*- ruby -*-
2
+
3
+ $LOAD_PATH << './lib'
4
+ require 'gravitext-util/version'
5
+
6
+ require 'rubygems'
7
+ gem 'rjack-tarpit', '~> 1.2.0'
8
+ require 'rjack-tarpit'
9
+
10
+ t = RJack::TarPit.new( 'gravitext-util',
11
+ Gravitext::Util::VERSION,
12
+ :no_assembly, :java_platform )
13
+
14
+ t.specify do |h|
15
+ h.developer( "David Kellum", "dek-oss@gravitext.com" )
16
+ h.extra_dev_deps += [ [ 'rjack-slf4j', '~> 1.5.8' ],
17
+ [ 'rjack-logback', '>= 0.9.17' ] ]
18
+ h.rubyforge_name = "gravitext"
19
+ end
20
+
21
+ file 'Manifest.txt' => [ 'lib/gravitext-util/version.rb' ]
22
+
23
+ task :check_pom_version do
24
+ t.test_line_match( 'pom.xml', /<version>/, /#{t.version}/ )
25
+ end
26
+ task :check_history_version do
27
+ t.test_line_match( 'History.rdoc', /^==/, / #{t.version} / )
28
+ end
29
+ task :check_history_date do
30
+ t.test_line_match( 'History.rdoc', /^==/, /\([0-9\-]+\)$/ )
31
+ end
32
+
33
+ task :gem => [ :check_pom_version, :check_history_version ]
34
+ task :tag => [ :check_pom_version, :check_history_version, :check_history_date ]
35
+ task :push => [ :check_history_date ]
36
+
37
+ t.define_tasks
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env jruby
2
+ # -*- ruby -*-
3
+ #--
4
+ # Copyright (c) 2007-2010 David Kellum
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
7
+ # may not use this file except in compliance with the License. You
8
+ # may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15
+ # implied. See the License for the specific language governing
16
+ # permissions and limitations under the License.
17
+ #++
18
+
19
+ $LOAD_PATH.unshift File.join( File.dirname(__FILE__), "..", "lib" )
20
+
21
+ require 'gravitext-util'
22
+ require 'gravitext-util/perftest'
23
+ require 'java'
24
+
25
+ module TestCollection
26
+ include Gravitext
27
+ include Gravitext::Concurrent
28
+
29
+ import 'com.gravitext.htmap.perftests.HTMapPerfTest'
30
+ import 'com.gravitext.perftest.tests.EmptyPerfTest'
31
+ import 'com.gravitext.perftest.tests.SortPerfTest'
32
+ import 'com.gravitext.util.perftests.ByteBufferInputStreamPerfTest'
33
+ import 'com.gravitext.util.perftests.FastRandomPerfTest'
34
+ import 'com.gravitext.util.perftests.ResizableCharBufferWriterPerfTest'
35
+
36
+ def self.run
37
+ tests = TestCollection::lookup_factories( ARGV[0] || 'random' )
38
+
39
+ harness = PerfTest::Harness.new( tests )
40
+ # harness.do_per_run_timing = false #FIXME
41
+ # harness.thread_count = 2
42
+ # harness.final_runs = 10000
43
+
44
+ harness.execute
45
+ end
46
+
47
+ def self.lookup_factories( name )
48
+ case name
49
+ when 'random'
50
+ tests = FastRandomPerfTest::Mode.values.map do |mode|
51
+ FastRandomPerfTest.new( mode )
52
+ end
53
+
54
+ btest = BlockTestFactory.new do |run,fr|
55
+ 10_000.times do
56
+ rand( 1000 )
57
+ end
58
+ rand( 3 )
59
+ end
60
+ btest.name = 'Ruby-Kernel.rand'
61
+ tests << btest
62
+
63
+ btest = BlockTestFactory.new do |run,random|
64
+ 10_000.times do
65
+ random.next_int( 1000 )
66
+ end
67
+ random.next_int( 3 )
68
+ end
69
+ btest.name = 'Ruby-FAST'
70
+ tests << btest
71
+
72
+ tests
73
+ when 'htmap'
74
+ HTMapPerfTest::TEST_CLASSES.map do |cls|
75
+ HTMapPerfTest.new( cls )
76
+ end
77
+ when 'buffer-writer'
78
+ ResizableCharBufferWriterPerfTest::TEST_CLASSES.map do |cls|
79
+ ResizableCharBufferWriterPerfTest.new( cls )
80
+ end
81
+ when 'buffer-instream'
82
+ tests = ByteBufferInputStreamPerfTest::TEST_CLASSES.map do |cls|
83
+ ByteBufferInputStreamPerfTest.new( cls )
84
+ end
85
+ tests += ByteBufferInputStreamPerfTest::TEST_CLASSES.map do |cls|
86
+ t = ByteBufferInputStreamPerfTest.new( cls )
87
+ t.read_one = true
88
+ t
89
+ end
90
+ when 'empty'
91
+ btest = BlockTestFactory.new { 1 }
92
+ btest.name = 'RubyEmptyBlock'
93
+ [ EmptyPerfTest.new, REmptyTestFactory.new, btest ]
94
+ when 'sort'
95
+ btest = BlockTestFactory.new do
96
+ numbers = []
97
+ ( rand( 10000 ) + 10 ).times do
98
+ numbers << rand( 10000 )
99
+ end
100
+ numbers.sort!
101
+ numbers.first
102
+ end
103
+ btest.name = 'RubySort'
104
+ [ SortPerfTest.new, btest ]
105
+ end
106
+ end
107
+
108
+ class REmptyTestFactory
109
+ include Gravitext::Concurrent
110
+ include TestFactory
111
+ include TestRunnable
112
+
113
+ def name
114
+ 'RubyEmpty'
115
+ end
116
+
117
+ def create_test_runnable( seed )
118
+ self.class.new
119
+ end
120
+
121
+ def run_iteration( run )
122
+ 1
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ TestCollection.run
@@ -0,0 +1,29 @@
1
+ #--
2
+ # Copyright (c) 2007-2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'gravitext-util/version'
18
+ require 'java'
19
+
20
+ module Gravitext
21
+ module Util
22
+ require File.join( LIB_DIR, "gravitext-util-#{ VERSION }.jar" )
23
+
24
+ import 'com.gravitext.util.FastRandom'
25
+ end
26
+
27
+ require 'gravitext-util/concurrent'
28
+ require 'gravitext-util/unimap'
29
+ end
@@ -0,0 +1,110 @@
1
+ #--
2
+ # Copyright (c) 2007-2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'java'
18
+ require 'gravitext-util'
19
+
20
+ module Gravitext
21
+
22
+ # Provides a concurrent testing facility
23
+ module Concurrent
24
+ import 'com.gravitext.concurrent.TestExecutor'
25
+ import 'com.gravitext.concurrent.TestFactory'
26
+ import 'com.gravitext.concurrent.TestRunnable'
27
+
28
+ # Returns the number of available processor cores on the host.
29
+ def self.available_cores
30
+ Java::java.lang.Runtime::runtime.available_processors
31
+ end
32
+
33
+ # Run TestRunnable instances created from (TestFactory)
34
+ # test_factory concurrently with threads (one TestRunnable
35
+ # per thread.). The first of any Exceptions raised in a test
36
+ # thread will be re-raised in the calling thread. Returns sum of
37
+ # runIteration return counts.
38
+ def self.execute_test_factory( test_factory, runs,
39
+ threads = available_cores )
40
+ TestExecutor::run( test_factory, runs, threads )
41
+ end
42
+
43
+ # Run test_runnable_class instances concurrently in threads. The
44
+ # test_runnable_class should take a instance of FastRandom in its
45
+ # initialize(). The first of any Exceptions raised in a test
46
+ # thread will be re-raised in the calling thread. Returns sum of
47
+ # runIteration return counts.
48
+ def self.execute_runnable( test_runnable_class, runs,
49
+ threads = available_cores )
50
+ TestExecutor::run( BasicTestFactory.new( test_runnable_class ),
51
+ runs, threads )
52
+ end
53
+
54
+ # Run block concurrently in the specified number of threads. The
55
+ # first of any Exceptions raised in block will be re-raised in the
56
+ # calling thread. Returns sum of runIteration return counts.
57
+ #
58
+ # :call-seq:
59
+ # execute_test(runs,threads = available_cores) { |run,random| ... } -> Integer
60
+ def self.execute_test( runs, threads = available_cores, &block )
61
+ TestExecutor::run( BlockTestFactory.new( block ), runs, threads )
62
+ end
63
+
64
+ class BlockTestFactory
65
+ include TestFactory
66
+
67
+ attr_accessor :name
68
+
69
+ def initialize( proc = nil, &block )
70
+ @name = 'BlockTestFactory'
71
+ @block = proc || block
72
+ end
73
+
74
+ def create_test_runnable( seed )
75
+ BlockTestRunnable.new( seed, @block )
76
+ end
77
+ end
78
+
79
+ class BlockTestRunnable
80
+ include TestRunnable
81
+
82
+ def initialize( seed, block )
83
+ @block = block
84
+ @random = Gravitext::Util::FastRandom.new( seed )
85
+ end
86
+
87
+ def run_iteration( run )
88
+ @block.call( run, @random )
89
+ end
90
+ end
91
+
92
+ class BasicTestFactory
93
+ include TestFactory
94
+
95
+ def initialize( test_class )
96
+ @test_class = test_class
97
+ end
98
+
99
+ def name
100
+ @test_class.name
101
+ end
102
+
103
+ def create_test_runnable( seed )
104
+ @test_class.new( Gravitext::Util::FastRandom.new( seed ) )
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,408 @@
1
+ #--
2
+ # Copyright (c) 2007-2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'gravitext-util'
18
+
19
+ require 'ostruct'
20
+ require 'java'
21
+
22
+ module Gravitext
23
+
24
+ # Concurrent performance testing facility
25
+ module PerfTest
26
+
27
+ module CalcUtil #:nodoc: all
28
+
29
+ NaN = Java::java.lang.Double::NaN
30
+
31
+ def throughput_change( exec, prior = nil )
32
+ if prior
33
+ p = prior.mean_throughput
34
+ ( exec.mean_throughput - p ) / p
35
+ else
36
+ NaN
37
+ end
38
+ end
39
+
40
+ def latency_change( exec, prior = nil )
41
+ if prior
42
+ p = prior.mean_latency.seconds
43
+ ( exec.mean_latency.seconds - p ) / p
44
+ else
45
+ NaN
46
+ end
47
+ end
48
+ end
49
+
50
+ # Concurrent performance testing harness with support for adaptive
51
+ # warmup and comparison on multiple TestFactory instances.
52
+ class Harness
53
+ include CalcUtil
54
+ import 'com.gravitext.concurrent.TestExecutor'
55
+ import 'com.gravitext.util.Duration'
56
+
57
+ # Number of threads to test with (default: available cores)
58
+ attr_accessor :thread_count
59
+
60
+ # Target duration of warmup comparison iterations in seconds (default:8s)
61
+ attr_accessor :warmup_exec_target
62
+
63
+ # Minimum warmup time in seconds for each test factory (default: 25s)
64
+ attr_accessor :warmup_total_target
65
+
66
+ # Secondary warmup requirement: consectutive iteration
67
+ # throughputs within tollerance (default: 0.05)
68
+ attr_accessor :warmup_tolerance
69
+
70
+ # Number of final comparison iterations (default: 3)
71
+ attr_accessor :final_iterations
72
+
73
+ # Number of runs in final comparisons (default: computed from
74
+ # warmup and final_exec_target)
75
+ attr_accessor :final_runs
76
+
77
+ # Target duration of final comparison iterations in seconds (default: 10s)
78
+ attr_accessor :final_exec_target
79
+
80
+ # Test executation progress is sent to listener, see PrintListener.
81
+ attr_accessor :listener
82
+
83
+ # Do per run timing for mean latency. (default: true)
84
+ attr_accessor :do_per_run_timing
85
+
86
+ # Maximum difference in counts for final run counts to be
87
+ # alligned (default: 3.0)
88
+ attr_accessor :max_align_ratio
89
+
90
+ # Initialize given array of com.gravitext.concurrent.TestFactory
91
+ # instances.
92
+ def initialize( factories )
93
+ @factories = factories
94
+ @thread_count = Concurrent::available_cores
95
+
96
+ @warmup_exec_target = 8.0
97
+ @warmup_total_target = 25
98
+ @warmup_tolerance = 0.05
99
+
100
+ @final_iterations = 3
101
+ @final_runs = nil
102
+ @final_exec_target = 10.0
103
+
104
+ @max_align_ratio = 3.0
105
+ @do_per_run_timing = true
106
+
107
+ @listener = PrintListener.new
108
+ end
109
+
110
+ def execute
111
+ @listener.begin( self, @factories )
112
+
113
+ finals = warmup
114
+
115
+ run_counts = if @final_runs
116
+ Array.new( @factories.size, @final_runs )
117
+ else
118
+ counts = @factories.map do |factory|
119
+ exec = finals.detect { |e| e.factory == factory }
120
+ ( exec.mean_throughput * @final_exec_target ).to_i
121
+ end
122
+ align_counts( counts )
123
+ end
124
+
125
+ results = execute_comparisons( run_counts, @final_iterations )
126
+ sums = sum_results( results ) if @final_iterations > 1
127
+
128
+ @listener.comparisons_end( self, sums )
129
+ sums
130
+ end
131
+
132
+ def warmup
133
+
134
+ @listener.warmups_begin( self )
135
+
136
+ states = @factories.map do |factory|
137
+ s = OpenStruct.new
138
+ s.factory = factory
139
+ s.prior = nil
140
+ s.warm_time = 0.0
141
+ s
142
+ end
143
+
144
+ first = true
145
+ finals = []
146
+ until states.empty? do
147
+
148
+ @listener.warmup_next_series( self ) unless first
149
+
150
+ states,done = states.partition do |s|
151
+
152
+ # Cleanup before each run
153
+ Java::java.lang.System::gc
154
+ Java::java.lang.Thread::yield
155
+
156
+ runs = if s.prior
157
+ ( @warmup_exec_target * s.prior.runs_executed ) /
158
+ s.prior.duration.seconds
159
+ else
160
+ 1
161
+ end
162
+
163
+ executor = create_executor( s.factory, runs.to_i )
164
+ @listener.warmup_start_run( executor )
165
+ executor.run_test
166
+ @listener.warmup_complete_run( executor, s.prior )
167
+
168
+ # Test throughput change, and increment warm_time
169
+ s.warm_time += executor.duration.seconds
170
+ tchange = throughput_change( executor, s.prior )
171
+ s.prior = executor
172
+
173
+ ( ( s.warm_time < @warmup_total_target ) ||
174
+ ( tchange.abs > @warmup_tolerance ) )
175
+ end
176
+ finals += done.map { |s| s.prior }
177
+ first = false
178
+ end
179
+
180
+ @listener.warmups_end( finals )
181
+ finals
182
+ end
183
+
184
+ def align_counts( counts )
185
+ mean = ( counts.inject { |sum,c| sum + c } ) / counts.size
186
+
187
+ # Round to 2-significant digits
188
+ f = 1
189
+ ( f *= 10 ) while ( mean / f ) > 100
190
+ mean = ( mean.to_f / f ).round * f
191
+
192
+ if ( mean.to_f / counts.min ) > @max_align_ratio
193
+ counts
194
+ else
195
+ Array.new( counts.size, mean )
196
+ end
197
+ end
198
+
199
+ def execute_comparisons( run_counts, iterations )
200
+
201
+ results = Array.new( @factories.size ) { [] }
202
+
203
+ @listener.comparisons_begin( self, run_counts )
204
+
205
+ iterations.times do |iteration|
206
+ @listener.comparison_next_series( self ) unless iteration.zero?
207
+
208
+ Java::java.lang.System::gc
209
+ Java::java.lang.Thread::yield
210
+
211
+ @factories.each_index do |f|
212
+ executor = create_executor( @factories[f], run_counts[f] )
213
+ @listener.comparison_start_run( executor )
214
+ executor.run_test
215
+ @listener.comparison_complete_run( executor, results[0].last )
216
+ results[f] << executor
217
+ end
218
+ end
219
+
220
+ results
221
+ end
222
+
223
+ def sum_results( results )
224
+ sums = []
225
+
226
+ results.each do |runs|
227
+ sum = OpenStruct.new
228
+ sum.factory = runs.first.factory
229
+ sum.runs_target = 0
230
+ sum.duration = 0.0
231
+ sum.result_sum = 0
232
+ sum.runs_executed = 0
233
+ sum.mean_throughput = 0.0
234
+ sum.mean_latency = 0.0
235
+
236
+ runs.each do |exec|
237
+ sum.runs_target += exec.runs_target
238
+ sum.duration += exec.duration.seconds
239
+ sum.result_sum += exec.result_sum
240
+ sum.runs_executed += exec.runs_executed
241
+ sum.mean_throughput += exec.mean_throughput
242
+ sum.mean_latency += exec.mean_latency.seconds
243
+ end
244
+
245
+ sum.duration = Duration.new( sum.duration )
246
+ sum.mean_throughput /= runs.size
247
+ sum.mean_latency = Duration.new( sum.mean_latency / runs.size )
248
+
249
+ sums << sum
250
+ end
251
+
252
+ sums
253
+ end
254
+
255
+ def create_executor( factory, runs )
256
+ executor = TestExecutor.new( factory, runs, @thread_count )
257
+ executor.do_per_run_timing = @do_per_run_timing
258
+ executor
259
+ end
260
+
261
+ end
262
+
263
+ # Listen for various events from the Harness and print results to console
264
+ class PrintListener
265
+ include CalcUtil
266
+ import 'com.gravitext.util.Metric'
267
+
268
+ # Status is written via out << (default $stdout)
269
+ def initialize( out = $stdout )
270
+ @out = out
271
+ end
272
+
273
+ def begin( harness, factories )
274
+ @out << "Concurrent testing: #{harness.thread_count} threads."
275
+ new_line
276
+ @nwidth = ( factories.map { |f| f.name.length } << 4 ).max
277
+ end
278
+
279
+ def warmups_begin( harness )
280
+ @out << ( "Warmup min %gs (change tolerance: %g) per test:" %
281
+ [ harness.warmup_total_target, harness.warmup_tolerance ] )
282
+ new_line
283
+ print_header
284
+ end
285
+
286
+ def warmup_start_run( executor )
287
+ print_result_start( executor )
288
+ end
289
+
290
+ def warmup_complete_run( executor, prior )
291
+ print_result( executor, prior )
292
+ end
293
+
294
+ def warmup_next_series( harness )
295
+ print_separator
296
+ end
297
+
298
+ def warmups_end( final_executors )
299
+ new_line
300
+ end
301
+
302
+ def comparisons_begin( harness, run_counts )
303
+ @out << ( "Comparison runs (%d iterations):" %
304
+ [ harness.final_iterations ] )
305
+ new_line
306
+ print_header
307
+ end
308
+
309
+ def comparison_next_series( harness )
310
+ print_separator
311
+ end
312
+
313
+ def comparison_start_run( executor )
314
+ print_result_start( executor )
315
+ end
316
+
317
+ def comparison_complete_run( executor, prior )
318
+ print_result( executor, prior )
319
+ end
320
+
321
+ def comparisons_end( harness, executor_sums )
322
+ print_separator( '=' )
323
+ executor_sums.each_index do |s|
324
+ print_result_start( executor_sums[s] )
325
+ print_result( executor_sums[s], ( executor_sums.first unless s.zero? ) )
326
+ end
327
+ end
328
+
329
+ def new_line
330
+ @out << "\n"
331
+ end
332
+
333
+ def print_header
334
+ @out << ( "%-#{@nwidth}s %-6s %-7s %-6s %8s %10s(%6s) %-9s (%6s)" %
335
+ [ "Test",
336
+ "Count",
337
+ "Time",
338
+ "R Sum",
339
+ "~R Value",
340
+ "Throughput",
341
+ "Change",
342
+ "~Latency",
343
+ "Change" ] )
344
+ new_line
345
+ print_separator( '=' )
346
+ end
347
+
348
+ def print_separator( char = '-' )
349
+ @out << ( char * ( @nwidth + 69 ) )
350
+ new_line
351
+ end
352
+
353
+ def print_result_start( exec, out = @out )
354
+ out << ( "%-#{@nwidth}s %6s " %
355
+ [ exec.factory.name,
356
+ Metric::format( exec.runs_target ) ] )
357
+ end
358
+
359
+ def print_result( exec, prior = nil, out = @out )
360
+ out << ( "%7s %6s %6s/r %6sr/s (%6s) %7s/r (%6s)" %
361
+ [ exec.duration,
362
+ Metric::format( exec.result_sum.to_f ),
363
+ Metric::format( exec.result_sum.to_f /
364
+ exec.runs_executed ),
365
+ Metric::format( exec.mean_throughput ),
366
+ Metric::format_difference( throughput_change(exec,prior) ),
367
+ exec.mean_latency,
368
+ Metric::format_difference( latency_change(exec,prior) ) ] )
369
+ new_line
370
+ end
371
+
372
+ end
373
+
374
+ # Derivation of PrintListener for consise debug log output
375
+ class LogListener < PrintListener
376
+
377
+ # Send <<() to log.debug
378
+ class LogWriter
379
+ def initialize( log )
380
+ @log = log
381
+ end
382
+ def <<( msg )
383
+ @log.debug( msg )
384
+ end
385
+ end
386
+
387
+ def initialize( logger )
388
+ super( LogWriter.new( logger ) )
389
+ end
390
+
391
+ alias :orig_result_start :print_result_start
392
+ def print_result_start( exec ); end
393
+
394
+ # Print run start and result output on single log line
395
+ def print_result( exec, prior = nil )
396
+ line = ""
397
+ orig_result_start( exec, line )
398
+ super( exec, prior, line )
399
+ @out << line
400
+ end
401
+
402
+ def new_line; end
403
+ def print_separator( char = '-' ); end
404
+
405
+ end
406
+
407
+ end
408
+ end