activesupport 3.1.0.beta1 → 3.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (46) hide show
  1. data/CHANGELOG +33 -1
  2. data/README.rdoc +1 -1
  3. data/lib/active_support.rb +1 -1
  4. data/lib/active_support/buffered_logger.rb +9 -4
  5. data/lib/active_support/configurable.rb +9 -6
  6. data/lib/active_support/core_ext/array/conversions.rb +2 -2
  7. data/lib/active_support/core_ext/array/random_access.rb +1 -1
  8. data/lib/active_support/core_ext/array/uniq_by.rb +2 -3
  9. data/lib/active_support/core_ext/class/inheritable_attributes.rb +0 -2
  10. data/lib/active_support/core_ext/date/calculations.rb +3 -3
  11. data/lib/active_support/core_ext/date/conversions.rb +3 -2
  12. data/lib/active_support/core_ext/date_time/conversions.rb +8 -8
  13. data/lib/active_support/core_ext/hash/conversions.rb +2 -1
  14. data/lib/active_support/core_ext/hash/indifferent_access.rb +12 -0
  15. data/lib/active_support/core_ext/hash/slice.rb +1 -1
  16. data/lib/active_support/core_ext/integer/inflections.rb +7 -4
  17. data/lib/active_support/core_ext/kernel/reporting.rb +13 -1
  18. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +1 -0
  19. data/lib/active_support/core_ext/module/deprecation.rb +0 -2
  20. data/lib/active_support/core_ext/numeric/time.rb +2 -0
  21. data/lib/active_support/core_ext/object/blank.rb +3 -3
  22. data/lib/active_support/core_ext/object/duplicable.rb +2 -0
  23. data/lib/active_support/core_ext/object/try.rb +2 -0
  24. data/lib/active_support/core_ext/object/with_options.rb +8 -5
  25. data/lib/active_support/core_ext/string/behavior.rb +1 -2
  26. data/lib/active_support/core_ext/string/exclude.rb +1 -1
  27. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  28. data/lib/active_support/core_ext/time/calculations.rb +4 -3
  29. data/lib/active_support/core_ext/time/marshal.rb +1 -0
  30. data/lib/active_support/descendants_tracker.rb +9 -7
  31. data/lib/active_support/duration.rb +1 -0
  32. data/lib/active_support/hash_with_indifferent_access.rb +12 -4
  33. data/lib/active_support/inflector/methods.rb +5 -3
  34. data/lib/active_support/json/encoding.rb +4 -1
  35. data/lib/active_support/log_subscriber/test_helper.rb +1 -0
  36. data/lib/active_support/ordered_hash.rb +4 -0
  37. data/lib/active_support/secure_random.rb +3 -202
  38. data/lib/active_support/testing/performance.rb +227 -335
  39. data/lib/active_support/testing/performance/jruby.rb +115 -0
  40. data/lib/active_support/testing/performance/rubinius.rb +113 -0
  41. data/lib/active_support/testing/performance/ruby.rb +152 -0
  42. data/lib/active_support/testing/performance/ruby/mri.rb +59 -0
  43. data/lib/active_support/testing/performance/ruby/yarv.rb +57 -0
  44. data/lib/active_support/version.rb +1 -1
  45. data/lib/active_support/xml_mini.rb +4 -1
  46. metadata +8 -3
@@ -1,6 +1,5 @@
1
1
  class String
2
- # Enable more predictable duck-typing on String-like classes. See
3
- # Object#acts_like?.
2
+ # Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
4
3
  def acts_like_string?
5
4
  true
6
5
  end
@@ -1,5 +1,5 @@
1
1
  class String
2
- # The inverse of String#include?. Returns true if the string does not include the other string.
2
+ # The inverse of <tt>String#include?</tt>. Returns true if the string does not include the other string.
3
3
  def exclude?(string)
4
4
  !include?(string)
5
5
  end
@@ -1,7 +1,7 @@
1
1
  require 'active_support/string_inquirer'
2
2
 
3
3
  class String
4
- # Wraps the current string in the ActiveSupport::StringInquirer class,
4
+ # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
5
5
  # which gives you a prettier way to test for equality. Example:
6
6
  #
7
7
  # env = "production".inquiry
@@ -1,4 +1,5 @@
1
1
  require 'active_support/duration'
2
+ require 'active_support/core_ext/time/zones'
2
3
 
3
4
  class Time
4
5
  COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -187,7 +188,7 @@ class Time
187
188
  # Returns a new Time representing the start of the day (0:00)
188
189
  def beginning_of_day
189
190
  #(self - seconds_since_midnight).change(:usec => 0)
190
- change(:hour => 0, :min => 0, :sec => 0, :usec => 0)
191
+ change(:hour => 0)
191
192
  end
192
193
  alias :midnight :beginning_of_day
193
194
  alias :at_midnight :beginning_of_day
@@ -201,7 +202,7 @@ class Time
201
202
  # Returns a new Time representing the start of the month (1st of the month, 0:00)
202
203
  def beginning_of_month
203
204
  #self - ((self.mday-1).days + self.seconds_since_midnight)
204
- change(:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
205
+ change(:day => 1, :hour => 0)
205
206
  end
206
207
  alias :at_beginning_of_month :beginning_of_month
207
208
 
@@ -227,7 +228,7 @@ class Time
227
228
 
228
229
  # Returns a new Time representing the start of the year (1st of january, 0:00)
229
230
  def beginning_of_year
230
- change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0, :usec => 0)
231
+ change(:month => 1, :day => 1, :hour => 0)
231
232
  end
232
233
  alias :at_beginning_of_year :beginning_of_year
233
234
 
@@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
37
37
  time.instance_eval do
38
38
  if zone = defined?(@_zone) && remove_instance_variable('@_zone')
39
39
  ary = to_a
40
+ ary[0] += subsec if ary[0] == sec
40
41
  ary[-1] = zone
41
42
  utc? ? Time.utc(*ary) : Time.local(*ary)
42
43
  else
@@ -1,5 +1,3 @@
1
- require 'active_support/dependencies'
2
-
3
1
  module ActiveSupport
4
2
  # This module provides an internal implementation to track descendants
5
3
  # which is faster than iterating through ObjectSpace.
@@ -18,12 +16,16 @@ module ActiveSupport
18
16
  end
19
17
 
20
18
  def self.clear
21
- @@direct_descendants.each do |klass, descendants|
22
- if ActiveSupport::Dependencies.autoloaded?(klass)
23
- @@direct_descendants.delete(klass)
24
- else
25
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
19
+ if defined? ActiveSupport::Dependencies
20
+ @@direct_descendants.each do |klass, descendants|
21
+ if ActiveSupport::Dependencies.autoloaded?(klass)
22
+ @@direct_descendants.delete(klass)
23
+ else
24
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
25
+ end
26
26
  end
27
+ else
28
+ @@direct_descendants.clear
27
29
  end
28
30
  end
29
31
 
@@ -10,6 +10,7 @@ module ActiveSupport
10
10
  # 1.month.ago # equivalent to Time.now.advance(:months => -1)
11
11
  class Duration < BasicObject
12
12
  attr_accessor :value, :parts
13
+ delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8
13
14
 
14
15
  def initialize(value, parts) #:nodoc:
15
16
  @value, @parts = value, parts
@@ -10,6 +10,10 @@ module ActiveSupport
10
10
  true
11
11
  end
12
12
 
13
+ def with_indifferent_access
14
+ dup
15
+ end
16
+
13
17
  def initialize(constructor = {})
14
18
  if constructor.is_a?(Hash)
15
19
  super()
@@ -58,8 +62,12 @@ module ActiveSupport
58
62
  # hash_1.update(hash_2) # => {"key"=>"New Value!"}
59
63
  #
60
64
  def update(other_hash)
61
- other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
62
- self
65
+ if other_hash.is_a? HashWithIndifferentAccess
66
+ super(other_hash)
67
+ else
68
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
69
+ self
70
+ end
63
71
  end
64
72
 
65
73
  alias_method :merge!, :update
@@ -140,8 +148,8 @@ module ActiveSupport
140
148
  end
141
149
 
142
150
  def convert_value(value)
143
- if value.class == Hash
144
- self.class.new_from_hash_copying_default(value)
151
+ if value.is_a? Hash
152
+ value.nested_under_indifferent_access
145
153
  elsif value.is_a?(Array)
146
154
  value.dup.replace(value.map { |e| convert_value(e) })
147
155
  else
@@ -135,11 +135,13 @@ module ActiveSupport
135
135
  # ordinalize(2) # => "2nd"
136
136
  # ordinalize(1002) # => "1002nd"
137
137
  # ordinalize(1003) # => "1003rd"
138
+ # ordinalize(-11) # => "-11th"
139
+ # ordinalize(-1021) # => "-1021st"
138
140
  def ordinalize(number)
139
- if (11..13).include?(number.to_i % 100)
141
+ if (11..13).include?(number.to_i.abs % 100)
140
142
  "#{number}th"
141
143
  else
142
- case number.to_i % 10
144
+ case number.to_i.abs % 10
143
145
  when 1; "#{number}st"
144
146
  when 2; "#{number}nd"
145
147
  when 3; "#{number}rd"
@@ -148,4 +150,4 @@ module ActiveSupport
148
150
  end
149
151
  end
150
152
  end
151
- end
153
+ end
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/to_json'
2
2
  require 'active_support/core_ext/module/delegation'
3
3
  require 'active_support/deprecation'
4
4
  require 'active_support/json/variable'
5
+ require 'active_support/ordered_hash'
5
6
 
6
7
  require 'bigdecimal'
7
8
  require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -205,7 +206,9 @@ class Regexp
205
206
  end
206
207
 
207
208
  module Enumerable
208
- def as_json(options = nil) to_a end #:nodoc:
209
+ def as_json(options = nil) #:nodoc:
210
+ to_a.as_json(options)
211
+ end
209
212
  end
210
213
 
211
214
  class Array
@@ -1,5 +1,6 @@
1
1
  require 'active_support/log_subscriber'
2
2
  require 'active_support/buffered_logger'
3
+ require 'active_support/notifications'
3
4
 
4
5
  module ActiveSupport
5
6
  class LogSubscriber
@@ -43,6 +43,10 @@ module ActiveSupport
43
43
  end
44
44
  end
45
45
 
46
+ def nested_under_indifferent_access
47
+ self
48
+ end
49
+
46
50
  # Hash is ordered in Ruby 1.9!
47
51
  if RUBY_VERSION < '1.9'
48
52
 
@@ -1,205 +1,6 @@
1
- begin
2
- require 'securerandom'
3
- rescue LoadError
4
- end
1
+ require 'securerandom'
5
2
 
6
3
  module ActiveSupport
7
- if defined?(::SecureRandom)
8
- # Use Ruby's SecureRandom library if available.
9
- SecureRandom = ::SecureRandom # :nodoc:
10
- else
11
- # = Secure random number generator interface.
12
- #
13
- # This library is an interface for secure random number generator which is
14
- # suitable for generating session key in HTTP cookies, etc.
15
- #
16
- # It supports following secure random number generators.
17
- #
18
- # * openssl
19
- # * /dev/urandom
20
- # * Win32
21
- #
22
- # *Note*: This module is based on the SecureRandom library from Ruby 1.9,
23
- # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's
24
- # SecureRandom library.
25
- #
26
- # == Example
27
- #
28
- # # random hexadecimal string.
29
- # p SecureRandom.hex(10) # => "52750b30ffbc7de3b362"
30
- # p SecureRandom.hex(10) # => "92b15d6c8dc4beb5f559"
31
- # p SecureRandom.hex(11) # => "6aca1b5c58e4863e6b81b8"
32
- # p SecureRandom.hex(12) # => "94b2fff3e7fd9b9c391a2306"
33
- # p SecureRandom.hex(13) # => "39b290146bea6ce975c37cfc23"
34
- # ...
35
- #
36
- # # random base64 string.
37
- # p SecureRandom.base64(10) # => "EcmTPZwWRAozdA=="
38
- # p SecureRandom.base64(10) # => "9b0nsevdwNuM/w=="
39
- # p SecureRandom.base64(10) # => "KO1nIU+p9DKxGg=="
40
- # p SecureRandom.base64(11) # => "l7XEiFja+8EKEtY="
41
- # p SecureRandom.base64(12) # => "7kJSM/MzBJI+75j8"
42
- # p SecureRandom.base64(13) # => "vKLJ0tXBHqQOuIcSIg=="
43
- # ...
44
- #
45
- # # random binary string.
46
- # p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301"
47
- # p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337"
48
- # ...
49
- #
50
- module SecureRandom
51
-
52
- # Generates a random binary string.
53
- #
54
- # The argument n specifies the length of the result string.
55
- #
56
- # If n is not specified, 16 is assumed.
57
- # It may be larger in future.
58
- #
59
- # If secure random number generator is not available,
60
- # NotImplementedError is raised.
61
- #
62
- def self.random_bytes(n=nil)
63
- n ||= 16
64
-
65
- unless defined? OpenSSL
66
- begin
67
- require 'openssl'
68
- rescue LoadError
69
- end
70
- end
71
-
72
- if defined? OpenSSL::Random
73
- return OpenSSL::Random.random_bytes(n)
74
- end
75
-
76
- if !defined?(@has_urandom) || @has_urandom
77
- flags = File::RDONLY
78
- flags |= File::NONBLOCK if defined? File::NONBLOCK
79
- flags |= File::NOCTTY if defined? File::NOCTTY
80
- flags |= File::NOFOLLOW if defined? File::NOFOLLOW
81
- begin
82
- File.open("/dev/urandom", flags) {|f|
83
- unless f.stat.chardev?
84
- raise Errno::ENOENT
85
- end
86
- @has_urandom = true
87
- ret = f.readpartial(n)
88
- if ret.length != n
89
- raise NotImplementedError, "Unexpected partial read from random device"
90
- end
91
- return ret
92
- }
93
- rescue Errno::ENOENT
94
- @has_urandom = false
95
- end
96
- end
97
-
98
- unless defined?(@has_win32)
99
- begin
100
- require 'Win32API'
101
-
102
- crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
103
- @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
104
-
105
- hProvStr = " " * 4
106
- prov_rsa_full = 1
107
- crypt_verifycontext = 0xF0000000
108
-
109
- if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
110
- raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
111
- end
112
- @hProv, = hProvStr.unpack('L')
113
-
114
- @has_win32 = true
115
- rescue LoadError
116
- @has_win32 = false
117
- end
118
- end
119
- if @has_win32
120
- bytes = " " * n
121
- if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
122
- raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
123
- end
124
- return bytes
125
- end
126
-
127
- raise NotImplementedError, "No random device"
128
- end
129
-
130
- # Generates a random hex string.
131
- #
132
- # The argument n specifies the length of the random length.
133
- # The length of the result string is twice of n.
134
- #
135
- # If n is not specified, 16 is assumed.
136
- # It may be larger in future.
137
- #
138
- # If secure random number generator is not available,
139
- # NotImplementedError is raised.
140
- #
141
- def self.hex(n=nil)
142
- random_bytes(n).unpack("H*")[0]
143
- end
144
-
145
- # Generates a random base64 string.
146
- #
147
- # The argument n specifies the length of the random length.
148
- # The length of the result string is about 4/3 of n.
149
- #
150
- # If n is not specified, 16 is assumed.
151
- # It may be larger in future.
152
- #
153
- # If secure random number generator is not available,
154
- # NotImplementedError is raised.
155
- #
156
- def self.base64(n=nil)
157
- [random_bytes(n)].pack("m*").delete("\n")
158
- end
159
-
160
- # Generates a random number.
161
- #
162
- # If an positive integer is given as n,
163
- # SecureRandom.random_number returns an integer:
164
- # 0 <= SecureRandom.random_number(n) < n.
165
- #
166
- # If 0 is given or an argument is not given,
167
- # SecureRandom.random_number returns an float:
168
- # 0.0 <= SecureRandom.random_number() < 1.0.
169
- #
170
- def self.random_number(n=0)
171
- if 0 < n
172
- hex = n.to_s(16)
173
- hex = '0' + hex if (hex.length & 1) == 1
174
- bin = [hex].pack("H*")
175
- mask = bin[0]
176
- mask |= mask >> 1
177
- mask |= mask >> 2
178
- mask |= mask >> 4
179
- begin
180
- rnd = SecureRandom.random_bytes(bin.length)
181
- rnd[0] = rnd[0] & mask
182
- end until rnd < bin
183
- rnd.unpack("H*")[0].hex
184
- else
185
- # assumption: Float::MANT_DIG <= 64
186
- i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
187
- Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
188
- end
189
- end
190
-
191
- # Following code is based on David Garamond's GUID library for Ruby.
192
- def self.lastWin32ErrorMessage # :nodoc:
193
- get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
194
- format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
195
- format_message_ignore_inserts = 0x00000200
196
- format_message_from_system = 0x00001000
197
-
198
- code = get_last_error.call
199
- msg = "\0" * 1024
200
- len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
201
- msg[0, len].tr("\r", '').chomp
202
- end
203
- end
204
- end
4
+ # Use Ruby's SecureRandom library.
5
+ SecureRandom = ::SecureRandom # :nodoc:
205
6
  end
@@ -1,38 +1,83 @@
1
- begin
2
- require 'ruby-prof'
3
-
4
- require 'fileutils'
5
- require 'rails/version'
6
- require 'active_support/core_ext/class/delegating_attributes'
7
- require 'active_support/core_ext/string/inflections'
8
-
9
- module ActiveSupport
10
- module Testing
11
- module Performance
12
- DEFAULTS =
13
- if benchmark = ARGV.include?('--benchmark') # HAX for rake test
14
- { :benchmark => true,
15
- :runs => 4,
16
- :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time],
17
- :output => 'tmp/performance' }
18
- else
19
- { :benchmark => false,
20
- :runs => 1,
21
- :min_percent => 0.01,
22
- :metrics => [:process_time, :memory, :objects],
23
- :formats => [:flat, :graph_html, :call_tree],
24
- :output => 'tmp/performance' }
25
- end.freeze
26
-
27
- def self.included(base)
28
- base.superclass_delegating_accessor :profile_options
29
- base.profile_options = DEFAULTS
1
+ require 'fileutils'
2
+ require 'rails/version'
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/class/delegating_attributes'
5
+ require 'active_support/core_ext/string/inflections'
6
+ require 'action_view/helpers/number_helper'
7
+
8
+ module ActiveSupport
9
+ module Testing
10
+ module Performance
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ superclass_delegating_accessor :profile_options
15
+ self.profile_options = {}
16
+
17
+ if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
18
+ include ForMiniTest
19
+ else
20
+ include ForClassicTestUnit
21
+ end
22
+ end
23
+
24
+ # each implementation should define metrics and freeze the defaults
25
+ DEFAULTS =
26
+ if ARGV.include?('--benchmark') # HAX for rake test
27
+ { :runs => 4,
28
+ :output => 'tmp/performance',
29
+ :benchmark => true }
30
+ else
31
+ { :runs => 1,
32
+ :output => 'tmp/performance',
33
+ :benchmark => false }
30
34
  end
35
+
36
+ def full_profile_options
37
+ DEFAULTS.merge(profile_options)
38
+ end
31
39
 
32
- def full_test_name
33
- "#{self.class.name}##{method_name}"
40
+ def full_test_name
41
+ "#{self.class.name}##{method_name}"
42
+ end
43
+
44
+ module ForMiniTest
45
+ def run(runner)
46
+ @runner = runner
47
+
48
+ run_warmup
49
+ if full_profile_options && metrics = full_profile_options[:metrics]
50
+ metrics.each do |metric_name|
51
+ if klass = Metrics[metric_name.to_sym]
52
+ run_profile(klass.new)
53
+ end
54
+ end
55
+ end
56
+
57
+ return
34
58
  end
35
59
 
60
+ def run_test(metric, mode)
61
+ result = '.'
62
+ begin
63
+ run_callbacks :setup
64
+ setup
65
+ metric.send(mode) { __send__ method_name }
66
+ rescue Exception => e
67
+ result = @runner.puke(self.class, method_name, e)
68
+ ensure
69
+ begin
70
+ teardown
71
+ run_callbacks :teardown, :enumerator => :reverse_each
72
+ rescue Exception => e
73
+ result = @runner.puke(self.class, method_name, e)
74
+ end
75
+ end
76
+ result
77
+ end
78
+ end
79
+
80
+ module ForClassicTestUnit
36
81
  def run(result)
37
82
  return if method_name =~ /^default_test$/
38
83
 
@@ -40,11 +85,13 @@ begin
40
85
  @_result = result
41
86
 
42
87
  run_warmup
43
- if profile_options && metrics = profile_options[:metrics]
88
+ if full_profile_options && metrics = full_profile_options[:metrics]
44
89
  metrics.each do |metric_name|
45
90
  if klass = Metrics[metric_name.to_sym]
46
91
  run_profile(klass.new)
47
92
  result.add_run
93
+ else
94
+ puts '%20s: unsupported' % metric_name
48
95
  end
49
96
  end
50
97
  end
@@ -57,7 +104,7 @@ begin
57
104
  setup
58
105
  metric.send(mode) { __send__ @method_name }
59
106
  rescue ::Test::Unit::AssertionFailedError => e
60
- add_failure(e.message, e.backtrace)
107
+ add_failure(e.message, e.backtrace)
61
108
  rescue StandardError, ScriptError => e
62
109
  add_error(e)
63
110
  ensure
@@ -70,356 +117,201 @@ begin
70
117
  add_error(e)
71
118
  end
72
119
  end
120
+ end
73
121
 
74
- protected
75
- def run_warmup
76
- GC.start
77
-
78
- time = Metrics::Time.new
79
- run_test(time, :benchmark)
80
- puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
81
-
82
- GC.start
83
- end
122
+ protected
123
+ # overridden by each implementation
124
+ def run_gc; end
125
+
126
+ def run_warmup
127
+ run_gc
84
128
 
85
- def run_profile(metric)
86
- klass = profile_options[:benchmark] ? Benchmarker : Profiler
87
- performer = klass.new(self, metric)
129
+ time = Metrics::Time.new
130
+ run_test(time, :benchmark)
131
+ puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
88
132
 
89
- performer.run
90
- puts performer.report
91
- performer.record
92
- end
133
+ run_gc
134
+ end
135
+
136
+ def run_profile(metric)
137
+ klass = full_profile_options[:benchmark] ? Benchmarker : Profiler
138
+ performer = klass.new(self, metric)
139
+
140
+ performer.run
141
+ puts performer.report
142
+ performer.record
143
+ end
93
144
 
94
- class Performer
95
- delegate :run_test, :profile_options, :full_test_name, :to => :@harness
145
+ class Performer
146
+ delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness
96
147
 
97
- def initialize(harness, metric)
98
- @harness, @metric = harness, metric
99
- end
148
+ def initialize(harness, metric)
149
+ @harness, @metric, @supported = harness, metric, false
150
+ end
100
151
 
101
- def report
102
- rate = @total / profile_options[:runs]
152
+ def report
153
+ if @supported
154
+ rate = @total / full_profile_options[:runs]
103
155
  '%20s: %s' % [@metric.name, @metric.format(rate)]
156
+ else
157
+ '%20s: unsupported' % @metric.name
104
158
  end
105
-
106
- protected
107
- def output_filename
108
- "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}"
109
- end
110
159
  end
111
160
 
112
- class Benchmarker < Performer
113
- def run
114
- profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
115
- @total = @metric.total
116
- end
117
-
118
- def record
119
- avg = @metric.total / profile_options[:runs].to_i
120
- now = Time.now.utc.xmlschema
121
- with_output_file do |file|
122
- file.puts "#{avg},#{now},#{environment}"
123
- end
124
- end
125
-
126
- def environment
127
- unless defined? @env
128
- app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
129
-
130
- rails = Rails::VERSION::STRING
131
- if File.directory?('vendor/rails/.git')
132
- Dir.chdir('vendor/rails') do
133
- rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
134
- end
135
- end
136
-
137
- ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
138
- ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
139
-
140
- @env = [app, rails, ruby, RUBY_PLATFORM] * ','
141
- end
142
-
143
- @env
161
+ protected
162
+ def output_filename
163
+ "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}"
144
164
  end
145
-
146
- protected
147
- HEADER = 'measurement,created_at,app,rails,ruby,platform'
148
-
149
- def with_output_file
150
- fname = output_filename
151
-
152
- if new = !File.exist?(fname)
153
- FileUtils.mkdir_p(File.dirname(fname))
154
- end
155
-
156
- File.open(fname, 'ab') do |file|
157
- file.puts(HEADER) if new
158
- yield file
159
- end
160
- end
161
-
162
- def output_filename
163
- "#{super}.csv"
164
- end
165
+ end
166
+
167
+ # overridden by each implementation
168
+ class Profiler < Performer
169
+ def time_with_block
170
+ before = Time.now
171
+ yield
172
+ Time.now - before
165
173
  end
174
+
175
+ def run; end
176
+ def record; end
177
+ end
166
178
 
167
- class Profiler < Performer
168
- def initialize(*args)
169
- super
170
- @supported = @metric.measure_mode rescue false
171
- end
172
-
173
- def run
174
- return unless @supported
175
-
176
- RubyProf.measure_mode = @metric.measure_mode
177
- RubyProf.start
178
- RubyProf.pause
179
- profile_options[:runs].to_i.times { run_test(@metric, :profile) }
180
- @data = RubyProf.stop
181
- @total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time }
182
- end
179
+ class Benchmarker < Performer
180
+ def initialize(*args)
181
+ super
182
+ @supported = @metric.respond_to?('measure')
183
+ end
184
+
185
+ def run
186
+ return unless @supported
187
+
188
+ full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
189
+ @total = @metric.total
190
+ end
183
191
 
184
- def report
185
- if @supported
186
- super
187
- else
188
- '%20s: unsupported' % @metric.name
189
- end
192
+ def record
193
+ avg = @metric.total / full_profile_options[:runs].to_i
194
+ now = Time.now.utc.xmlschema
195
+ with_output_file do |file|
196
+ file.puts "#{avg},#{now},#{environment}"
190
197
  end
198
+ end
191
199
 
192
- def record
193
- return unless @supported
194
-
195
- klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact
200
+ def environment
201
+ unless defined? @env
202
+ app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
196
203
 
197
- klasses.each do |klass|
198
- fname = output_filename(klass)
199
- FileUtils.mkdir_p(File.dirname(fname))
200
- File.open(fname, 'wb') do |file|
201
- klass.new(@data).print(file, profile_options.slice(:min_percent))
204
+ rails = Rails::VERSION::STRING
205
+ if File.directory?('vendor/rails/.git')
206
+ Dir.chdir('vendor/rails') do
207
+ rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
202
208
  end
203
209
  end
204
- end
205
210
 
206
- protected
207
- def output_filename(printer_class)
208
- suffix =
209
- case printer_class.name.demodulize
210
- when 'FlatPrinter'; 'flat.txt'
211
- when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt'
212
- when 'GraphPrinter'; 'graph.txt'
213
- when 'GraphHtmlPrinter'; 'graph.html'
214
- when 'GraphYamlPrinter'; 'graph.yml'
215
- when 'CallTreePrinter'; 'tree.txt'
216
- when 'CallStackPrinter'; 'stack.html'
217
- when 'DotPrinter'; 'graph.dot'
218
- else printer_class.name.sub(/Printer$/, '').underscore
219
- end
220
-
221
- "#{super()}_#{suffix}"
222
- end
223
- end
211
+ ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
212
+ ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
224
213
 
225
- module Metrics
226
- def self.[](name)
227
- const_get(name.to_s.camelize)
228
- rescue NameError
229
- nil
214
+ @env = [app, rails, ruby, RUBY_PLATFORM] * ','
230
215
  end
231
216
 
232
- class Base
233
- attr_reader :total
234
-
235
- def initialize
236
- @total = 0
237
- end
238
-
239
- def name
240
- @name ||= self.class.name.demodulize.underscore
241
- end
217
+ @env
218
+ end
242
219
 
243
- def measure_mode
244
- self.class::Mode
245
- end
220
+ protected
221
+ HEADER = 'measurement,created_at,app,rails,ruby,platform'
246
222
 
247
- def measure
248
- 0
249
- end
223
+ def with_output_file
224
+ fname = output_filename
250
225
 
251
- def benchmark
252
- with_gc_stats do
253
- before = measure
254
- yield
255
- @total += (measure - before)
256
- end
226
+ if new = !File.exist?(fname)
227
+ FileUtils.mkdir_p(File.dirname(fname))
257
228
  end
258
229
 
259
- def profile
260
- RubyProf.resume
261
- yield
262
- ensure
263
- RubyProf.pause
230
+ File.open(fname, 'ab') do |file|
231
+ file.puts(HEADER) if new
232
+ yield file
264
233
  end
265
-
266
- protected
267
- # Ruby 1.9 with GC::Profiler
268
- if defined?(GC::Profiler)
269
- def with_gc_stats
270
- GC::Profiler.enable
271
- GC.start
272
- yield
273
- ensure
274
- GC::Profiler.disable
275
- end
276
-
277
- # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
278
- elsif GC.respond_to?(:enable_stats)
279
- def with_gc_stats
280
- GC.enable_stats
281
- yield
282
- ensure
283
- GC.disable_stats
284
- end
285
-
286
- else
287
- def with_gc_stats
288
- yield
289
- end
290
- end
291
234
  end
292
235
 
293
- class Time < Base
294
- def measure
295
- ::Time.now.to_f
296
- end
297
-
298
- def format(measurement)
299
- if measurement < 1
300
- '%d ms' % (measurement * 1000)
301
- else
302
- '%.2f sec' % measurement
303
- end
304
- end
236
+ def output_filename
237
+ "#{super}.csv"
305
238
  end
239
+ end
240
+
241
+ module Metrics
242
+ def self.[](name)
243
+ const_get(name.to_s.camelize)
244
+ rescue NameError
245
+ nil
246
+ end
306
247
 
307
- class ProcessTime < Time
308
- Mode = RubyProf::PROCESS_TIME
248
+ class Base
249
+ include ActionView::Helpers::NumberHelper
250
+
251
+ attr_reader :total
309
252
 
310
- def measure
311
- RubyProf.measure_process_time
312
- end
253
+ def initialize
254
+ @total = 0
313
255
  end
314
256
 
315
- class WallTime < Time
316
- Mode = RubyProf::WALL_TIME
317
-
318
- def measure
319
- RubyProf.measure_wall_time
320
- end
257
+ def name
258
+ @name ||= self.class.name.demodulize.underscore
321
259
  end
322
260
 
323
- class CpuTime < Time
324
- Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME)
325
-
326
- def initialize(*args)
327
- # FIXME: yeah my CPU is 2.33 GHz
328
- RubyProf.cpu_frequency = 2.33e9 unless RubyProf.cpu_frequency > 0
329
- super
330
- end
331
-
332
- def measure
333
- RubyProf.measure_cpu_time
261
+ def benchmark
262
+ with_gc_stats do
263
+ before = measure
264
+ yield
265
+ @total += (measure - before)
334
266
  end
335
267
  end
336
-
337
- class Memory < Base
338
- Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
339
-
340
- # Ruby 1.9 + GCdata patch
341
- if GC.respond_to?(:malloc_allocated_size)
342
- def measure
343
- GC.malloc_allocated_size / 1024.0
344
- end
345
-
346
- # Ruby 1.8 + ruby-prof wrapper
347
- elsif RubyProf.respond_to?(:measure_memory)
348
- def measure
349
- RubyProf.measure_memory / 1024.0
350
- end
351
- end
352
-
353
- def format(measurement)
354
- '%.2f KB' % measurement
355
- end
268
+
269
+ # overridden by each implementation
270
+ def profile; end
271
+
272
+ protected
273
+ # overridden by each implementation
274
+ def with_gc_stats; end
275
+ end
276
+
277
+ class Time < Base
278
+ def measure
279
+ ::Time.now.to_f
356
280
  end
357
281
 
358
- class Objects < Base
359
- Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
360
-
361
- # Ruby 1.9 + GCdata patch
362
- if GC.respond_to?(:malloc_allocations)
363
- def measure
364
- GC.malloc_allocations
365
- end
366
-
367
- # Ruby 1.8 + ruby-prof wrapper
368
- elsif RubyProf.respond_to?(:measure_allocations)
369
- def measure
370
- RubyProf.measure_allocations
371
- end
372
- end
373
-
374
- def format(measurement)
375
- measurement.to_i.to_s
282
+ def format(measurement)
283
+ if measurement < 1
284
+ '%d ms' % (measurement * 1000)
285
+ else
286
+ '%.2f sec' % measurement
376
287
  end
377
288
  end
378
-
379
- class GcRuns < Base
380
- Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
381
-
382
- # Ruby 1.9
383
- if GC.respond_to?(:count)
384
- def measure
385
- GC.count
386
- end
387
-
388
- # Ruby 1.8 + ruby-prof wrapper
389
- elsif RubyProf.respond_to?(:measure_gc_runs)
390
- def measure
391
- RubyProf.measure_gc_runs
392
- end
393
- end
394
-
395
- def format(measurement)
396
- measurement.to_i.to_s
397
- end
289
+ end
290
+
291
+ class Amount < Base
292
+ def format(measurement)
293
+ number_with_delimiter(measurement.floor)
398
294
  end
399
-
400
- class GcTime < Base
401
- Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
402
-
403
- # Ruby 1.9 with GC::Profiler
404
- if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
405
- def measure
406
- GC::Profiler.total_time
407
- end
408
-
409
- # Ruby 1.8 + ruby-prof wrapper
410
- elsif RubyProf.respond_to?(:measure_gc_time)
411
- def measure
412
- RubyProf.measure_gc_time / 1000
413
- end
414
- end
415
-
416
- def format(measurement)
417
- '%.2f ms' % measurement
418
- end
295
+ end
296
+
297
+ class DigitalInformationUnit < Base
298
+ def format(measurement)
299
+ number_to_human_size(measurement, :precision => 2)
419
300
  end
420
301
  end
302
+
303
+ # each implementation provides its own metrics like ProcessTime, Memory or GcRuns
421
304
  end
422
305
  end
423
306
  end
424
- rescue LoadError
307
+ end
308
+
309
+ RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8
310
+ case RUBY_ENGINE
311
+ when 'ruby' then require 'active_support/testing/performance/ruby'
312
+ when 'rbx' then require 'active_support/testing/performance/rubinius'
313
+ when 'jruby' then require 'active_support/testing/performance/jruby'
314
+ else
315
+ $stderr.puts 'Your ruby interpreter is not supported for benchmarking.'
316
+ exit
425
317
  end