rack-mini-profiler 0.1.22 → 0.1.23
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.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- data/Ruby/README.md +22 -9
- data/Ruby/lib/html/includes.js +78 -15
- data/Ruby/lib/html/includes.tmpl +39 -15
- data/Ruby/lib/html/jquery.1.7.1.js +1 -1
- data/Ruby/lib/html/jquery.tmpl.js +1 -1
- data/Ruby/lib/html/list.js +7 -6
- data/Ruby/lib/html/profile_handler.js +1 -62
- data/Ruby/lib/mini_profiler/client_timer_struct.rb +1 -1
- data/Ruby/lib/mini_profiler/config.rb +54 -52
- data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
- data/Ruby/lib/mini_profiler/page_timer_struct.rb +7 -2
- data/Ruby/lib/mini_profiler/profiler.rb +61 -17
- data/Ruby/lib/mini_profiler/request_timer_struct.rb +20 -1
- data/Ruby/lib/mini_profiler/storage/redis_store.rb +44 -44
- data/Ruby/lib/mini_profiler/version.rb +5 -0
- data/Ruby/lib/mini_profiler_rails/railtie.rb +1 -1
- data/Ruby/lib/patches/net_patches.rb +14 -0
- data/Ruby/lib/patches/sql_patches.rb +20 -1
- data/Ruby/lib/rack-mini-profiler.rb +1 -0
- data/rack-mini-profiler.gemspec +1 -1
- metadata +7 -4
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
# Timing system for a custom timers such as cache, redis, RPC, external API
|
7
|
+
# calls, etc.
|
8
|
+
class CustomTimerStruct < TimerStruct
|
9
|
+
def initialize(type, duration_ms, page, parent)
|
10
|
+
@parent = parent
|
11
|
+
@page = page
|
12
|
+
@type = type
|
13
|
+
|
14
|
+
super("Type" => type,
|
15
|
+
"StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
|
16
|
+
"DurationMilliseconds" => duration_ms,
|
17
|
+
"ParentTimingId" => nil)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -7,6 +7,7 @@ module Rack
|
|
7
7
|
# Root: RequestTimer
|
8
8
|
# :has_many RequestTimer children
|
9
9
|
# :has_many SqlTimer children
|
10
|
+
# :has_many CustomTimer children
|
10
11
|
class PageTimerStruct < TimerStruct
|
11
12
|
def initialize(env)
|
12
13
|
super("Id" => MiniProfiler.generate_id,
|
@@ -27,7 +28,10 @@ module Rack
|
|
27
28
|
"HasDuplicateSqlTimings" => false,
|
28
29
|
"ExecutedReaders" => 0,
|
29
30
|
"ExecutedScalars" => 0,
|
30
|
-
"ExecutedNonQueries" => 0
|
31
|
+
"ExecutedNonQueries" => 0,
|
32
|
+
"CustomTimingNames" => [],
|
33
|
+
"CustomTimingStats" => {}
|
34
|
+
)
|
31
35
|
name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
32
36
|
self['Root'] = RequestTimerStruct.createRoot(name, self)
|
33
37
|
end
|
@@ -43,7 +47,8 @@ module Rack
|
|
43
47
|
def to_json(*a)
|
44
48
|
attribs = @attributes.merge(
|
45
49
|
"Started" => '/Date(%d)/' % @attributes['Started'],
|
46
|
-
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
|
50
|
+
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
|
51
|
+
"CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
|
47
52
|
)
|
48
53
|
::JSON.generate(attribs, :max_nesting => 100)
|
49
54
|
end
|
@@ -2,8 +2,10 @@ require 'json'
|
|
2
2
|
require 'timeout'
|
3
3
|
require 'thread'
|
4
4
|
|
5
|
+
require 'mini_profiler/version'
|
5
6
|
require 'mini_profiler/page_timer_struct'
|
6
7
|
require 'mini_profiler/sql_timer_struct'
|
8
|
+
require 'mini_profiler/custom_timer_struct'
|
7
9
|
require 'mini_profiler/client_timer_struct'
|
8
10
|
require 'mini_profiler/request_timer_struct'
|
9
11
|
require 'mini_profiler/storage/abstract_store'
|
@@ -19,11 +21,8 @@ require 'mini_profiler/gc_profiler'
|
|
19
21
|
|
20
22
|
module Rack
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
# we really should add a cleaner way to version JS and includes
|
25
|
-
VERSION = '108'.freeze
|
26
|
-
|
24
|
+
class MiniProfiler
|
25
|
+
|
27
26
|
class << self
|
28
27
|
|
29
28
|
include Rack::MiniProfiler::ProfilingMethods
|
@@ -79,6 +78,29 @@ module Rack
|
|
79
78
|
def request_authorized?
|
80
79
|
Thread.current[:mp_authorized]
|
81
80
|
end
|
81
|
+
|
82
|
+
# Add a custom timing. These are displayed similar to SQL/query time in
|
83
|
+
# columns expanding to the right.
|
84
|
+
#
|
85
|
+
# type - String counter type. Each distinct type gets its own column.
|
86
|
+
# duration_ms - Duration of the call in ms. Either this or a block must be
|
87
|
+
# given but not both.
|
88
|
+
#
|
89
|
+
# When a block is given, calculate the duration by yielding to the block
|
90
|
+
# and keeping a record of its run time.
|
91
|
+
#
|
92
|
+
# Returns the result of the block, or nil when no block is given.
|
93
|
+
def counter(type, duration_ms=nil)
|
94
|
+
result = nil
|
95
|
+
if block_given?
|
96
|
+
start = Time.now
|
97
|
+
result = yield
|
98
|
+
duration_ms = (Time.now - start).to_f * 1000
|
99
|
+
end
|
100
|
+
return result if current.nil? || !request_authorized?
|
101
|
+
current.current_timer.add_custom(type, duration_ms, current.page_struct)
|
102
|
+
result
|
103
|
+
end
|
82
104
|
end
|
83
105
|
|
84
106
|
#
|
@@ -127,7 +149,7 @@ module Rack
|
|
127
149
|
html.gsub!(/\{json\}/, result_json)
|
128
150
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
129
151
|
html.gsub!(/\{name\}/, page_struct['Name'])
|
130
|
-
html.gsub!(/\{duration\}/, page_struct.duration_ms
|
152
|
+
html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
|
131
153
|
|
132
154
|
[200, {'Content-Type' => 'text/html'}, [html]]
|
133
155
|
end
|
@@ -209,6 +231,8 @@ module Rack
|
|
209
231
|
client_settings.disable_profiling = true
|
210
232
|
client_settings.write!(headers)
|
211
233
|
return [status,headers,body]
|
234
|
+
else
|
235
|
+
client_settings.disable_profiling = false
|
212
236
|
end
|
213
237
|
|
214
238
|
if query_string =~ /pp=profile-gc/
|
@@ -316,6 +340,7 @@ module Rack
|
|
316
340
|
end
|
317
341
|
|
318
342
|
page_struct = current.page_struct
|
343
|
+
page_struct['User'] = user(env)
|
319
344
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
320
345
|
|
321
346
|
if backtraces
|
@@ -325,7 +350,7 @@ module Rack
|
|
325
350
|
|
326
351
|
|
327
352
|
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
328
|
-
@storage.set_unviewed(
|
353
|
+
@storage.set_unviewed(page_struct['User'], page_struct['Id'])
|
329
354
|
@storage.save(page_struct)
|
330
355
|
|
331
356
|
# inject headers, script
|
@@ -369,13 +394,29 @@ module Rack
|
|
369
394
|
end
|
370
395
|
|
371
396
|
def inject(fragment, script)
|
372
|
-
fragment.
|
373
|
-
#
|
374
|
-
|
397
|
+
if fragment.match(/<\/body>/i)
|
398
|
+
# explicit </body>
|
399
|
+
|
400
|
+
regex = /<\/body>/i
|
401
|
+
close_tag = '</body>'
|
402
|
+
elsif fragment.match(/<\/html>/i)
|
403
|
+
# implicit </body>
|
404
|
+
|
405
|
+
regex = /<\/html>/i
|
406
|
+
close_tag = '</html>'
|
407
|
+
else
|
408
|
+
# implicit </body> and </html>. Just append the script.
|
409
|
+
|
410
|
+
return fragment + script
|
411
|
+
end
|
412
|
+
|
413
|
+
fragment.sub(regex) do
|
414
|
+
# if for whatever crazy reason we dont get a utf string,
|
415
|
+
# just force the encoding, no utf in the mp scripts anyway
|
375
416
|
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
376
|
-
(script +
|
417
|
+
(script + close_tag).force_encoding(fragment.encoding)
|
377
418
|
else
|
378
|
-
script +
|
419
|
+
script + close_tag
|
379
420
|
end
|
380
421
|
end
|
381
422
|
end
|
@@ -449,6 +490,12 @@ module Rack
|
|
449
490
|
::JSON.generate(ids.uniq)
|
450
491
|
end
|
451
492
|
|
493
|
+
def ids_comma_separated(env)
|
494
|
+
# cap at 10 ids, otherwise there is a chance you can blow the header
|
495
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
|
496
|
+
ids.join(",")
|
497
|
+
end
|
498
|
+
|
452
499
|
# get_profile_script returns script to be injected inside current html page
|
453
500
|
# By default, profile_script is appended to the end of all html requests automatically.
|
454
501
|
# Calling get_profile_script cancels automatic append for the current page
|
@@ -456,7 +503,7 @@ module Rack
|
|
456
503
|
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
457
504
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
458
505
|
def get_profile_script(env)
|
459
|
-
ids =
|
506
|
+
ids = ids_comma_separated(env)
|
460
507
|
path = @config.base_url_path
|
461
508
|
version = MiniProfiler::VERSION
|
462
509
|
position = @config.position
|
@@ -466,16 +513,13 @@ module Rack
|
|
466
513
|
showControls = false
|
467
514
|
currentId = current.page_struct["Id"]
|
468
515
|
authorized = true
|
469
|
-
useExistingjQuery = @config.use_existing_jquery
|
470
516
|
# TODO : cache this snippet
|
471
517
|
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
472
518
|
# replace the variables
|
473
|
-
[:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized
|
519
|
+
[:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized].each do |v|
|
474
520
|
regex = Regexp.new("\\{#{v.to_s}\\}")
|
475
521
|
script.gsub!(regex, eval(v.to_s).to_s)
|
476
522
|
end
|
477
|
-
# replace the '{{' and '}}''
|
478
|
-
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
479
523
|
current.inject_js = false
|
480
524
|
script
|
481
525
|
end
|
@@ -33,7 +33,9 @@ module Rack
|
|
33
33
|
"Depth"=> parent ? parent.depth + 1 : 0,
|
34
34
|
"ExecutedReaders"=> 0,
|
35
35
|
"ExecutedScalars"=> 0,
|
36
|
-
"ExecutedNonQueries"=> 0
|
36
|
+
"ExecutedNonQueries"=> 0,
|
37
|
+
"CustomTimingStats" => {},
|
38
|
+
"CustomTimings" => {})
|
37
39
|
@children_duration = 0
|
38
40
|
@start = Time.now
|
39
41
|
@parent = parent
|
@@ -79,6 +81,23 @@ module Rack
|
|
79
81
|
timer
|
80
82
|
end
|
81
83
|
|
84
|
+
def add_custom(type, elapsed_ms, page)
|
85
|
+
timer = CustomTimerStruct.new(type, elapsed_ms, page, self)
|
86
|
+
timer['ParentTimingId'] = self['Id']
|
87
|
+
self['CustomTimings'][type] ||= []
|
88
|
+
self['CustomTimings'][type].push(timer)
|
89
|
+
|
90
|
+
self['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
|
91
|
+
self['CustomTimingStats'][type]['Count'] += 1
|
92
|
+
self['CustomTimingStats'][type]['Duration'] += elapsed_ms
|
93
|
+
|
94
|
+
page['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
|
95
|
+
page['CustomTimingStats'][type]['Count'] += 1
|
96
|
+
page['CustomTimingStats'][type]['Duration'] += elapsed_ms
|
97
|
+
|
98
|
+
timer
|
99
|
+
end
|
100
|
+
|
82
101
|
def record_time(milliseconds = nil)
|
83
102
|
milliseconds ||= (Time.now - @start) * 1000
|
84
103
|
self['DurationMilliseconds'] = milliseconds
|
@@ -1,44 +1,44 @@
|
|
1
|
-
module Rack
|
2
|
-
class MiniProfiler
|
3
|
-
class RedisStore < AbstractStore
|
4
|
-
|
5
|
-
EXPIRE_SECONDS = 60 * 60 * 24
|
6
|
-
|
7
|
-
def initialize(args)
|
8
|
-
args
|
9
|
-
@prefix = args
|
10
|
-
end
|
11
|
-
|
12
|
-
def save(page_struct)
|
13
|
-
redis.setex "#{@prefix}#{page_struct['Id']}", EXPIRE_SECONDS, Marshal::dump(page_struct)
|
14
|
-
end
|
15
|
-
|
16
|
-
def load(id)
|
17
|
-
raw = redis.get "#{@prefix}#{id}"
|
18
|
-
if raw
|
19
|
-
Marshal::load raw
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def set_unviewed(user, id)
|
24
|
-
redis.sadd "#{@prefix}-#{user}-v", id
|
25
|
-
end
|
26
|
-
|
27
|
-
def set_viewed(user, id)
|
28
|
-
redis.srem "#{@prefix}-#{user}-v", id
|
29
|
-
end
|
30
|
-
|
31
|
-
def get_unviewed_ids(user)
|
32
|
-
redis.smembers "#{@prefix}-#{user}-v"
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def redis
|
38
|
-
require 'redis' unless defined? Redis
|
39
|
-
Redis.new
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class RedisStore < AbstractStore
|
4
|
+
|
5
|
+
EXPIRE_SECONDS = 60 * 60 * 24
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@args = args || {}
|
9
|
+
@prefix = @args.delete(:prefix) || 'MPRedisStore'
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(page_struct)
|
13
|
+
redis.setex "#{@prefix}#{page_struct['Id']}", EXPIRE_SECONDS, Marshal::dump(page_struct)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(id)
|
17
|
+
raw = redis.get "#{@prefix}#{id}"
|
18
|
+
if raw
|
19
|
+
Marshal::load raw
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_unviewed(user, id)
|
24
|
+
redis.sadd "#{@prefix}-#{user}-v", id
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_viewed(user, id)
|
28
|
+
redis.srem "#{@prefix}-#{user}-v", id
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_unviewed_ids(user)
|
32
|
+
redis.smembers "#{@prefix}-#{user}-v"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def redis
|
38
|
+
require 'redis' unless defined? Redis
|
39
|
+
Redis.new @args
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -9,7 +9,7 @@ module MiniProfilerRails
|
|
9
9
|
|
10
10
|
# By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
|
11
11
|
c.pre_authorize_cb = lambda { |env|
|
12
|
-
Rails.env.
|
12
|
+
!Rails.env.test?
|
13
13
|
}
|
14
14
|
|
15
15
|
c.skip_paths ||= []
|
@@ -0,0 +1,14 @@
|
|
1
|
+
if (defined?(Net) && defined?(Net::HTTP))
|
2
|
+
|
3
|
+
Net::HTTP.class_eval do
|
4
|
+
def request_with_mini_profiler(*args, &block)
|
5
|
+
request = args[0]
|
6
|
+
Rack::MiniProfiler.step("Net::HTTP #{request.method} #{request.path}") do
|
7
|
+
request_without_mini_profiler(*args, &block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
alias request_without_mini_profiler request
|
11
|
+
alias request request_with_mini_profiler
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -169,6 +169,25 @@ if SqlPatches.class_exists? "PG::Result"
|
|
169
169
|
end
|
170
170
|
|
171
171
|
|
172
|
+
# Mongoid 3 patches
|
173
|
+
if SqlPatches.class_exists?("Moped::Node")
|
174
|
+
class Moped::Node
|
175
|
+
alias_method :process_without_profiling, :process
|
176
|
+
def process(*args,&blk)
|
177
|
+
current = ::Rack::MiniProfiler.current
|
178
|
+
return process_without_profiling(*args,&blk) unless current
|
179
|
+
|
180
|
+
start = Time.now
|
181
|
+
result = process_without_profiling(*args,&blk)
|
182
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
183
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0].log_inspect, elapsed_time))
|
184
|
+
|
185
|
+
result
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
172
191
|
|
173
192
|
# Fallback for sequel
|
174
193
|
if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
|
@@ -186,7 +205,7 @@ end
|
|
186
205
|
|
187
206
|
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
188
207
|
## fallback for alls sorts of weird dbs
|
189
|
-
if SqlPatches.module_exists?('ActiveRecord')
|
208
|
+
if SqlPatches.module_exists?('ActiveRecord') && !SqlPatches.patched?
|
190
209
|
module Rack
|
191
210
|
class MiniProfiler
|
192
211
|
module ActiveRecordInstrumentation
|
data/rack-mini-profiler.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rack-mini-profiler"
|
3
|
-
s.version = "0.1.
|
3
|
+
s.version = "0.1.23"
|
4
4
|
s.summary = "Profiles loading speed for rack applications."
|
5
5
|
s.authors = ["Sam Saffron", "Robin Ward","Aleks Totic"]
|
6
6
|
s.description = "Profiling toolkit for Rack applications with Rails integration. Client Side profiling, DB profiling and Server profiling."
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-mini-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.23
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-11-15 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rack
|
@@ -87,6 +87,7 @@ extra_rdoc_files:
|
|
87
87
|
- Ruby/CHANGELOG
|
88
88
|
files:
|
89
89
|
- rack-mini-profiler.gemspec
|
90
|
+
- Ruby/lib/mini_profiler/version.rb
|
90
91
|
- Ruby/lib/mini_profiler/sql_timer_struct.rb
|
91
92
|
- Ruby/lib/mini_profiler/request_timer_struct.rb
|
92
93
|
- Ruby/lib/mini_profiler/timer_struct.rb
|
@@ -103,6 +104,7 @@ files:
|
|
103
104
|
- Ruby/lib/mini_profiler/storage/redis_store.rb
|
104
105
|
- Ruby/lib/mini_profiler/storage/abstract_store.rb
|
105
106
|
- Ruby/lib/mini_profiler/storage/memcache_store.rb
|
107
|
+
- Ruby/lib/mini_profiler/custom_timer_struct.rb
|
106
108
|
- Ruby/lib/rack-mini-profiler.rb
|
107
109
|
- Ruby/lib/html/jquery.1.7.1.js
|
108
110
|
- Ruby/lib/html/includes.tmpl
|
@@ -117,6 +119,7 @@ files:
|
|
117
119
|
- Ruby/lib/html/list.js
|
118
120
|
- Ruby/lib/mini_profiler_rails/railtie.rb
|
119
121
|
- Ruby/lib/patches/sql_patches.rb
|
122
|
+
- Ruby/lib/patches/net_patches.rb
|
120
123
|
- Ruby/README.md
|
121
124
|
- Ruby/CHANGELOG
|
122
125
|
homepage: http://miniprofiler.com
|
@@ -133,7 +136,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
136
|
version: '0'
|
134
137
|
segments:
|
135
138
|
- 0
|
136
|
-
hash: -
|
139
|
+
hash: -387077425
|
137
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
141
|
none: false
|
139
142
|
requirements:
|
@@ -142,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
145
|
version: '0'
|
143
146
|
segments:
|
144
147
|
- 0
|
145
|
-
hash: -
|
148
|
+
hash: -387077425
|
146
149
|
requirements: []
|
147
150
|
rubyforge_project:
|
148
151
|
rubygems_version: 1.8.24
|