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 +36 -0
- data/Manifest.txt +16 -0
- data/NOTICE.txt +2 -0
- data/README.rdoc +45 -0
- data/Rakefile +37 -0
- data/bin/gravitext-perftest +128 -0
- data/lib/gravitext-util.rb +29 -0
- data/lib/gravitext-util/concurrent.rb +110 -0
- data/lib/gravitext-util/gravitext-util-1.4.0.jar +0 -0
- data/lib/gravitext-util/perftest.rb +408 -0
- data/lib/gravitext-util/unimap.rb +79 -0
- data/lib/gravitext-util/version.rb +23 -0
- data/pom.xml +115 -0
- data/test/test_concurrent.rb +91 -0
- data/test/test_perftest.rb +53 -0
- data/test/test_unimap.rb +102 -0
- metadata +120 -0
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
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
|
Binary file
|
@@ -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
|