redis-stat 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/redis-stat/constants.rb +6 -5
- data/lib/redis-stat/option.rb +2 -5
- data/lib/redis-stat/server/public/js/site.js +11 -9
- data/lib/redis-stat/server/views/index.erb +15 -19
- data/lib/redis-stat/server.rb +31 -15
- data/lib/redis-stat/version.rb +1 -1
- data/lib/redis-stat.rb +17 -10
- data/redis-stat.gemspec +10 -7
- metadata +14 -14
data/lib/redis-stat/constants.rb
CHANGED
@@ -9,14 +9,15 @@ class RedisStat
|
|
9
9
|
MEASURES = {
|
10
10
|
:static => [
|
11
11
|
:redis_version,
|
12
|
+
:redis_mode,
|
12
13
|
:process_id,
|
13
14
|
:uptime_in_seconds,
|
14
15
|
:uptime_in_days,
|
15
|
-
:gcc_version,
|
16
16
|
:role,
|
17
17
|
:connected_slaves,
|
18
18
|
:aof_enabled,
|
19
|
-
:
|
19
|
+
[:rdb_bgsave_in_progress, :bgsave_in_progress],
|
20
|
+
[:rdb_last_save_time, :last_save_time],
|
20
21
|
],
|
21
22
|
:default => [
|
22
23
|
:at,
|
@@ -57,7 +58,7 @@ class RedisStat
|
|
57
58
|
:keyspace_misses,
|
58
59
|
:aof_current_size,
|
59
60
|
:aof_base_size,
|
60
|
-
:changes_since_last_save,
|
61
|
+
[:rdb_changes_since_last_save, :changes_since_last_save],
|
61
62
|
:pubsub_channels,
|
62
63
|
:pubsub_patterns,
|
63
64
|
]
|
@@ -85,7 +86,7 @@ class RedisStat
|
|
85
86
|
:keyspace_misses_per_second => [:magenta],
|
86
87
|
:aof_current_size => [:cyan],
|
87
88
|
:aof_base_size => [:cyan],
|
88
|
-
:
|
89
|
+
:rdb_changes_since_last_save => [:green, :bold],
|
89
90
|
:pubsub_channels => [:cyan, :bold],
|
90
91
|
:pubsub_patterns => [:cyan, :bold],
|
91
92
|
}
|
@@ -112,7 +113,7 @@ class RedisStat
|
|
112
113
|
:keyspace_misses_per_second => 'mis/s',
|
113
114
|
:aof_current_size => 'aofcs',
|
114
115
|
:aof_base_size => 'aofbs',
|
115
|
-
:
|
116
|
+
:rdb_changes_since_last_save => 'chsv',
|
116
117
|
:pubsub_channels => 'psch',
|
117
118
|
:pubsub_patterns => 'psp',
|
118
119
|
}
|
data/lib/redis-stat/option.rb
CHANGED
@@ -42,9 +42,6 @@ module Option
|
|
42
42
|
|
43
43
|
opts.on('--server[=PORT]', "Launch redis-stat web server (default port: #{RedisStat::DEFAULT_SERVER_PORT})") do |v|
|
44
44
|
options[:server_port] = v || RedisStat::DEFAULT_SERVER_PORT
|
45
|
-
if RUBY_PLATFORM == 'java'
|
46
|
-
raise ArgumentError.new("Sorry. redis-stat server is not supported in JRuby.")
|
47
|
-
end
|
48
45
|
end
|
49
46
|
|
50
47
|
opts.on('--daemon', "Daemonize redis-stat. Must be used with --server option.") do |v|
|
@@ -66,7 +63,7 @@ module Option
|
|
66
63
|
exit 0
|
67
64
|
end
|
68
65
|
}
|
69
|
-
|
66
|
+
|
70
67
|
begin
|
71
68
|
opts.parse! argv
|
72
69
|
|
@@ -125,7 +122,7 @@ module Option
|
|
125
122
|
raise ArgumentError.new("Invalid style")
|
126
123
|
end
|
127
124
|
end
|
128
|
-
|
125
|
+
|
129
126
|
if options[:daemon] && options[:server_port].nil?
|
130
127
|
raise ArgumentError.new("--daemon option must be used in conjunction with --server option")
|
131
128
|
end
|
@@ -5,8 +5,7 @@ var history = [],
|
|
5
5
|
colors,
|
6
6
|
info,
|
7
7
|
fade_dur,
|
8
|
-
chart_options
|
9
|
-
stats_to_update = ['uptime_in_seconds', 'uptime_in_days']
|
8
|
+
chart_options
|
10
9
|
|
11
10
|
var initialize = function(params) {
|
12
11
|
measures = params.measures
|
@@ -89,7 +88,7 @@ var initialize = function(params) {
|
|
89
88
|
shadow: false,
|
90
89
|
},
|
91
90
|
series: [
|
92
|
-
{ label: "
|
91
|
+
{ label: "mem" },
|
93
92
|
{ label: "rss" }
|
94
93
|
],
|
95
94
|
axesDefaults: {
|
@@ -134,8 +133,13 @@ var initialize = function(params) {
|
|
134
133
|
}
|
135
134
|
|
136
135
|
var appendToHistory = function(json) {
|
137
|
-
history.
|
138
|
-
|
136
|
+
if (history.length == 0 || json.at > history[ history.length - 1 ].at) {
|
137
|
+
history.push(json)
|
138
|
+
if (history.length > max) history.shift()
|
139
|
+
return true
|
140
|
+
} else {
|
141
|
+
return false
|
142
|
+
}
|
139
143
|
}
|
140
144
|
|
141
145
|
var updateTable = function() {
|
@@ -144,12 +148,10 @@ var updateTable = function() {
|
|
144
148
|
jd = json.dynamic
|
145
149
|
|
146
150
|
// Instance information (mostly static)
|
147
|
-
|
148
|
-
for (var i = 0; i < stats_to_update.length; ++i) {
|
149
|
-
var stat = stats_to_update[i]
|
151
|
+
for (var stat in js) {
|
150
152
|
$("#" + stat).replaceWith(
|
151
153
|
"<tr id='" + stat + "'><th>" + stat + "</th>" +
|
152
|
-
js[stat].map(function(e) { return "<td>" + e + "</td>" }).join() +
|
154
|
+
js[stat].map(function(e) { return "<td>" + (e == null ? "" : e) + "</td>" }).join() +
|
153
155
|
"</tr>"
|
154
156
|
)
|
155
157
|
}
|
@@ -26,6 +26,11 @@
|
|
26
26
|
<div class="row">
|
27
27
|
<div class="span12">
|
28
28
|
<h3>Dashboard</h3>
|
29
|
+
<% unless RUBY_PLATFORM == 'java' %>
|
30
|
+
<div class="alert alert-error hide">
|
31
|
+
<h4>Lost connection to redis-stat</h4>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
29
34
|
</div>
|
30
35
|
</div>
|
31
36
|
|
@@ -80,7 +85,7 @@
|
|
80
85
|
</tr>
|
81
86
|
</thead>
|
82
87
|
<tbody>
|
83
|
-
<%
|
88
|
+
<% @tab_measures.each do |stat| %>
|
84
89
|
<tr id="<%= stat %>">
|
85
90
|
<th>
|
86
91
|
<%= stat %>
|
@@ -103,19 +108,6 @@
|
|
103
108
|
<img style="position: fixed; top: 0; right: 0; border: 0; z-index: 10000" src="/forkme.png" alt="Fork me on GitHub">
|
104
109
|
</a>
|
105
110
|
|
106
|
-
<div class="modal hide fade" id="fail" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
107
|
-
<div class="modal-header">
|
108
|
-
<h3 id="myModalLabel">Oops!</h3>
|
109
|
-
</div>
|
110
|
-
<div class="modal-body">
|
111
|
-
<p>Lost connection to redis-stat.</p>
|
112
|
-
<p>Try reloading the page.</p>
|
113
|
-
</div>
|
114
|
-
<div class="modal-footer">
|
115
|
-
<a href="/" class="btn btn-primary">Reload</a>
|
116
|
-
</div>
|
117
|
-
</div>
|
118
|
-
|
119
111
|
<footer class="footer">
|
120
112
|
<div class="container">
|
121
113
|
<p>Powered by <a href="http://twitter.github.com/bootstrap/">Bootstrap</a>, <a href="http://jquery.com/">jQuery</a> and <a href="http://www.jqplot.com/">jqPlot</a>.</p>
|
@@ -134,15 +126,19 @@
|
|
134
126
|
|
135
127
|
$("#stat_table th a").tooltip()
|
136
128
|
|
129
|
+
// TODO Check (typeof(EventSource) !== "undefined")
|
130
|
+
|
137
131
|
var source = new EventSource("/pull")
|
138
132
|
source.onmessage = function(e) {
|
139
|
-
appendToHistory(JSON.parse(e.data))
|
140
|
-
|
141
|
-
|
133
|
+
if (appendToHistory(JSON.parse(e.data))) {
|
134
|
+
$(".alert").fadeOut()
|
135
|
+
updateTable()
|
136
|
+
updatePlot()
|
137
|
+
}
|
142
138
|
}
|
143
139
|
source.onerror = function(e) {
|
144
|
-
|
145
|
-
|
140
|
+
$(".alert").fadeIn()
|
141
|
+
// source.close()
|
146
142
|
}
|
147
143
|
})
|
148
144
|
</script>
|
data/lib/redis-stat/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'sinatra/base'
|
2
2
|
require 'json'
|
3
|
+
require 'thread'
|
3
4
|
|
4
5
|
class RedisStat
|
5
6
|
class Server < Sinatra::Base
|
@@ -7,7 +8,10 @@ class Server < Sinatra::Base
|
|
7
8
|
STAT_TABLE_ROWS = 10
|
8
9
|
|
9
10
|
configure do
|
10
|
-
|
11
|
+
if RUBY_PLATFORM == 'java'
|
12
|
+
require 'puma'
|
13
|
+
set :server, :puma
|
14
|
+
else
|
11
15
|
require 'thin'
|
12
16
|
set :server, :thin
|
13
17
|
end
|
@@ -15,24 +19,32 @@ class Server < Sinatra::Base
|
|
15
19
|
set :root, File.join( File.dirname(__FILE__), 'server' )
|
16
20
|
set :clients, []
|
17
21
|
set :history, []
|
22
|
+
set :mutex, Mutex.new
|
18
23
|
end
|
19
24
|
|
20
25
|
get '/' do
|
21
|
-
@hosts
|
22
|
-
@info
|
23
|
-
@measures
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@
|
26
|
+
@hosts = settings.redis_stat.hosts
|
27
|
+
@info = settings.redis_stat.info
|
28
|
+
@measures = settings.redis_stat.measures
|
29
|
+
@tab_measures = settings.redis_stat.tab_measures
|
30
|
+
@interval = settings.redis_stat.interval
|
31
|
+
@verbose = settings.redis_stat.verbose ? 'verbose' : ''
|
32
|
+
@history = settings.history
|
27
33
|
erb :index
|
28
34
|
end
|
29
35
|
|
30
36
|
get '/pull' do
|
31
37
|
content_type 'text/event-stream'
|
32
38
|
|
33
|
-
|
34
|
-
settings.
|
35
|
-
|
39
|
+
if RUBY_PLATFORM == 'java'
|
40
|
+
if last = settings.mutex.synchronize { settings.history.last }
|
41
|
+
body "retry: #{settings.redis_stat.interval * 900}\ndata: #{last.to_json}\n\n"
|
42
|
+
end
|
43
|
+
else
|
44
|
+
stream(:keep_open) do |out|
|
45
|
+
settings.clients << out
|
46
|
+
out.callback { settings.clients.delete out }
|
47
|
+
end
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
@@ -44,14 +56,18 @@ class Server < Sinatra::Base
|
|
44
56
|
end
|
45
57
|
|
46
58
|
def push info, data
|
47
|
-
static = Hash[
|
59
|
+
static = Hash[settings.redis_stat.tab_measures.map { |stat|
|
48
60
|
[stat, info[stat]]
|
49
61
|
}]
|
50
|
-
data = {:static => static, :dynamic => data}
|
62
|
+
data = {:at => Time.now.to_i, :static => static, :dynamic => data}
|
63
|
+
|
64
|
+
hist = settings.history
|
65
|
+
settings.mutex.synchronize do
|
66
|
+
hist << data
|
67
|
+
hist.shift if hist.length > HISTORY_LENGTH
|
68
|
+
end
|
51
69
|
|
52
|
-
|
53
|
-
@history << data
|
54
|
-
@history.shift if @history.length > HISTORY_LENGTH
|
70
|
+
return if settings.clients.empty?
|
55
71
|
|
56
72
|
resp = "data: #{data.to_json}\n\n"
|
57
73
|
settings.clients.each do |cl|
|
data/lib/redis-stat/version.rb
CHANGED
data/lib/redis-stat.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'redis-stat/version'
|
4
4
|
require 'redis-stat/constants'
|
5
5
|
require 'redis-stat/option'
|
6
|
-
require 'redis-stat/server'
|
6
|
+
require 'redis-stat/server'
|
7
7
|
require 'insensitive_hash'
|
8
8
|
require 'redis'
|
9
9
|
require 'tabularize'
|
@@ -13,12 +13,12 @@ require 'parallelize'
|
|
13
13
|
require 'si'
|
14
14
|
|
15
15
|
class RedisStat
|
16
|
-
attr_reader :hosts, :measures, :verbose, :interval
|
16
|
+
attr_reader :hosts, :measures, :tab_measures, :verbose, :interval
|
17
17
|
|
18
18
|
def initialize options = {}
|
19
19
|
options = RedisStat::Option::DEFAULT.merge options
|
20
20
|
@hosts = options[:hosts]
|
21
|
-
@redises = @hosts.map { |e|
|
21
|
+
@redises = @hosts.map { |e|
|
22
22
|
host, port = e.split(':')
|
23
23
|
Redis.new(Hash[ {:host => host, :port => port, :timeout => DEFAULT_REDIS_TIMEOUT}.select { |k, v| v } ])
|
24
24
|
}
|
@@ -29,7 +29,8 @@ class RedisStat
|
|
29
29
|
@csv = options[:csv]
|
30
30
|
@auth = options[:auth]
|
31
31
|
@verbose = options[:verbose]
|
32
|
-
@measures = MEASURES[ @verbose ? :verbose : :default ]
|
32
|
+
@measures = MEASURES[ @verbose ? :verbose : :default ].map { |m| [*m].first }
|
33
|
+
@tab_measures= MEASURES[:static].map { |m| [*m].first }
|
33
34
|
@all_measures= MEASURES.values.inject(:+).uniq - [:at]
|
34
35
|
@count = 0
|
35
36
|
@style = options[:style]
|
@@ -72,9 +73,12 @@ class RedisStat
|
|
72
73
|
rescue Interrupt
|
73
74
|
raise
|
74
75
|
rescue Exception => e
|
75
|
-
|
76
|
+
errs += 1
|
77
|
+
if server || errs < NUM_RETRIES
|
76
78
|
@os.puts if errs == 1
|
77
|
-
@os.puts ansi(:red, :bold) {
|
79
|
+
@os.puts ansi(:red, :bold) {
|
80
|
+
"#{e} (#{ server ? "#{errs}" : [errs, NUM_RETRIES].join('/') })"
|
81
|
+
}
|
78
82
|
sleep @interval
|
79
83
|
retry
|
80
84
|
else
|
@@ -128,8 +132,11 @@ private
|
|
128
132
|
redis.info.insensitive
|
129
133
|
}.each do |rinfo|
|
130
134
|
(@all_measures + rinfo.keys.select { |k| k =~ /^db[0-9]+$/ }).each do |k|
|
135
|
+
ks = [*k]
|
136
|
+
v = ks.map { |e| rinfo[e] }.compact.first
|
137
|
+
k = ks.first
|
131
138
|
info[k] ||= []
|
132
|
-
info[k] <<
|
139
|
+
info[k] << v
|
133
140
|
end
|
134
141
|
end
|
135
142
|
end
|
@@ -241,7 +248,7 @@ private
|
|
241
248
|
)
|
242
249
|
tab << [nil] + @hosts.map { |h| ansi(:bold, :green) { h } }
|
243
250
|
tab.separator!
|
244
|
-
|
251
|
+
@tab_measures.each do |key|
|
245
252
|
tab << [ansi(:bold) { key }] + info[key] unless info[key].compact.empty?
|
246
253
|
end
|
247
254
|
@os.puts tab
|
@@ -260,7 +267,7 @@ private
|
|
260
267
|
table << info_output.map { |pair|
|
261
268
|
ansi(*((@colors[pair.first] || []) + [:underline])) {
|
262
269
|
LABELS[pair.first] || pair.first
|
263
|
-
}
|
270
|
+
}
|
264
271
|
}
|
265
272
|
table.separator!
|
266
273
|
table
|
@@ -292,7 +299,7 @@ private
|
|
292
299
|
val &&= (val * 100).round
|
293
300
|
[humanize_number(val), val]
|
294
301
|
when :keys
|
295
|
-
val = Hash[ info.select { |k, v| k =~ /^db[0-9]+$/ } ].values.inject(0) { |sum, vs|
|
302
|
+
val = Hash[ info.select { |k, v| k =~ /^db[0-9]+$/ } ].values.inject(0) { |sum, vs|
|
296
303
|
sum + vs.map { |v| Hash[ v.split(',').map { |e| e.split '=' } ]['keys'].to_i }.inject(:+)
|
297
304
|
}
|
298
305
|
[humanize_number(val), val]
|
data/redis-stat.gemspec
CHANGED
@@ -17,15 +17,18 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.version = RedisStat::VERSION
|
18
18
|
|
19
19
|
gem.add_runtime_dependency "ansi", '~> 1.4.3'
|
20
|
-
gem.add_runtime_dependency "redis", '~> 3.0.
|
21
|
-
gem.add_runtime_dependency "tabularize", '~> 0.2.
|
20
|
+
gem.add_runtime_dependency "redis", '~> 3.0.2'
|
21
|
+
gem.add_runtime_dependency "tabularize", '~> 0.2.9'
|
22
22
|
gem.add_runtime_dependency "insensitive_hash", '~> 0.3.0'
|
23
23
|
gem.add_runtime_dependency "parallelize", '~> 0.4.0'
|
24
|
-
gem.add_runtime_dependency "si", '~> 0.1.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
gem.add_runtime_dependency "si", '~> 0.1.4'
|
25
|
+
gem.add_runtime_dependency "sinatra", '~> 1.3.3'
|
26
|
+
gem.add_runtime_dependency "json", '~> 1.7.5'
|
27
|
+
|
28
|
+
if RUBY_PLATFORM == 'java'
|
29
|
+
gem.add_runtime_dependency "puma", '~> 1.6.3'
|
30
|
+
else
|
31
|
+
gem.add_runtime_dependency "thin", '~> 1.5.0'
|
29
32
|
gem.add_runtime_dependency "daemons", '~> 1.1.9'
|
30
33
|
end
|
31
34
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-stat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ansi
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 3.0.
|
37
|
+
version: 3.0.2
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 3.0.
|
45
|
+
version: 3.0.2
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: tabularize
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - ~>
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.2.
|
53
|
+
version: 0.2.9
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.2.
|
61
|
+
version: 0.2.9
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: insensitive_hash
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,7 +98,7 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: 0.1.
|
101
|
+
version: 0.1.4
|
102
102
|
type: :runtime
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -106,7 +106,7 @@ dependencies:
|
|
106
106
|
requirements:
|
107
107
|
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: 0.1.
|
109
|
+
version: 0.1.4
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: sinatra
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,13 +124,13 @@ dependencies:
|
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: 1.3.3
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
|
-
name:
|
127
|
+
name: json
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
129
|
none: false
|
130
130
|
requirements:
|
131
131
|
- - ~>
|
132
132
|
- !ruby/object:Gem::Version
|
133
|
-
version: 1.5
|
133
|
+
version: 1.7.5
|
134
134
|
type: :runtime
|
135
135
|
prerelease: false
|
136
136
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -138,15 +138,15 @@ dependencies:
|
|
138
138
|
requirements:
|
139
139
|
- - ~>
|
140
140
|
- !ruby/object:Gem::Version
|
141
|
-
version: 1.5
|
141
|
+
version: 1.7.5
|
142
142
|
- !ruby/object:Gem::Dependency
|
143
|
-
name:
|
143
|
+
name: thin
|
144
144
|
requirement: !ruby/object:Gem::Requirement
|
145
145
|
none: false
|
146
146
|
requirements:
|
147
147
|
- - ~>
|
148
148
|
- !ruby/object:Gem::Version
|
149
|
-
version: 1.
|
149
|
+
version: 1.5.0
|
150
150
|
type: :runtime
|
151
151
|
prerelease: false
|
152
152
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -154,7 +154,7 @@ dependencies:
|
|
154
154
|
requirements:
|
155
155
|
- - ~>
|
156
156
|
- !ruby/object:Gem::Version
|
157
|
-
version: 1.
|
157
|
+
version: 1.5.0
|
158
158
|
- !ruby/object:Gem::Dependency
|
159
159
|
name: daemons
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|