nutcracker 0.2.4.8 → 0.2.4.9
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/README.md +7 -1
- data/lib/nutcracker.rb +82 -61
- data/lib/nutcracker/version.rb +1 -1
- metadata +7 -6
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
This library wraps Twitter's [Nutcracker](https://github.com/twitter/twemproxy) in a gem package and provides a simple ruby API to the `nutcracker` executable.
|
5
5
|
|
6
6
|
### Disclaimer
|
7
|
-
this project is still in its early stages
|
7
|
+
this project is still in its early stages so things could be a little bit buggy, if you find one feel free to [report](https://github.com/kontera-technologies/nutcracker/issues) it.
|
8
8
|
|
9
9
|
## Motivation
|
10
10
|
The main motivation here is to take the advantages of working with Bundler's dependencies management and to be able to embed Twitter's [Nutcracker](https://github.com/twitter/twemproxy) as a dependency to any Ruby project, this allow you to create small-configuration-only-apps tied to specific version of Nutcracker as I show in the example bellow.
|
@@ -68,6 +68,12 @@ nutcracker.start
|
|
68
68
|
nutcracker.join # wait for server to exit
|
69
69
|
```
|
70
70
|
|
71
|
+
you can also attach to a running instance of nutcracker
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
Nutcracker.attach(config_file: 'cluster.yaml', stats_port: 22222)
|
75
|
+
```
|
76
|
+
|
71
77
|
### Building new gems
|
72
78
|
* Set the version @ `lib/nutcracker/version.rb` ( [Available Versions](https://code.google.com/p/twemproxy/downloads/list) )
|
73
79
|
* run the `rake build` command
|
data/lib/nutcracker.rb
CHANGED
@@ -5,128 +5,133 @@ require 'yaml'
|
|
5
5
|
require 'redis'
|
6
6
|
|
7
7
|
module Nutcracker
|
8
|
-
|
8
|
+
# Syntactic sugar for launching the Nutcracker service ( see {Wrapper#initialize} )
|
9
|
+
# @return [Wrapper] Nutcracker process wrapper
|
10
|
+
# @example
|
11
|
+
# Nutcracker.start config_file: 'conf/nutcracker.yaml'
|
9
12
|
def self.start options
|
10
13
|
Nutcracker::Wrapper.new(options).start
|
11
14
|
end
|
12
|
-
|
15
|
+
|
16
|
+
# Connect to a running instance of Nutcracker ( see {Wrapper#initialize} )
|
17
|
+
# @return [Wrapper] Nutcracker process wrapper
|
18
|
+
# @example
|
19
|
+
# Nutcracker.attach :config_file: 'conf/nutcracker.yaml', :stats_port => 22222
|
20
|
+
def self.attach options
|
21
|
+
Nutcracker::Wrapper.new options.merge attached: true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the Nutcracker executable path that is embeded with the gem
|
13
25
|
def self.executable
|
14
26
|
File.expand_path("../../ext/nutcracker/src/nutcracker", __FILE__)
|
15
27
|
end
|
16
28
|
|
29
|
+
# Returns the version string
|
17
30
|
def self.version
|
18
31
|
Nutcracker::VERSION
|
19
32
|
end
|
20
33
|
|
21
34
|
class Wrapper
|
22
|
-
attr_reader :pid
|
35
|
+
attr_reader :pid
|
23
36
|
|
37
|
+
# Initialize a new Nutcracker process wrappper
|
38
|
+
# @param [Hash] options
|
39
|
+
# @option options [String] :config_file (conf/nutcracker.yaml) path to nutcracker's configuration file
|
40
|
+
# @option options [String] :stats_port (22222) Nutcracker stats listing port
|
41
|
+
# @option options [Array] :args ([]) array with additional command line arguments
|
24
42
|
def initialize options
|
25
|
-
@
|
43
|
+
@options = validate defaults.merge options
|
26
44
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
45
|
+
|
46
|
+
# launching the Nutcracker service
|
47
|
+
def start *args
|
48
|
+
return if attached? or running?
|
49
|
+
@pid = ::Process.spawn Nutcracker.executable, *command
|
31
50
|
Kernel.at_exit { kill if running? }
|
32
51
|
self
|
33
52
|
end
|
34
|
-
|
53
|
+
|
54
|
+
# Returns the current running status
|
35
55
|
def running?
|
36
|
-
!!(pid and ::Process.getpgid pid rescue false)
|
56
|
+
attached? ? stats.any? : !!(pid and ::Process.getpgid pid rescue false)
|
37
57
|
end
|
38
|
-
|
58
|
+
|
59
|
+
# Returns true if the current instance was initialize with the attached flag
|
60
|
+
def attached?
|
61
|
+
@options[:attached]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Stops the Nutcracker service
|
39
65
|
def stop
|
40
66
|
sig :TERM
|
41
67
|
end
|
42
|
-
|
68
|
+
|
69
|
+
# Kills the Nutcracker service
|
43
70
|
def kill
|
44
71
|
sig :KILL
|
45
72
|
end
|
46
|
-
|
73
|
+
|
74
|
+
# Wait for the process to exit
|
47
75
|
def join
|
48
|
-
running! and ::Process.waitpid2 pid
|
76
|
+
attached? ? sleep : (running! and ::Process.waitpid2 pid)
|
49
77
|
end
|
50
78
|
|
79
|
+
# Returns Nutcracker's configuration hash
|
51
80
|
def config
|
52
|
-
@config ||= YAML.load_file config_file
|
81
|
+
@config ||= YAML.load_file @options[:config_file]
|
53
82
|
end
|
54
83
|
|
55
|
-
#
|
84
|
+
# Syntactic sugar for initialize plugins
|
56
85
|
def use plugin, *args
|
57
86
|
Nutcracker.const_get(plugin.to_s.capitalize).start(self,*args)
|
58
87
|
end
|
59
|
-
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# :clusters => [
|
63
|
-
# {
|
64
|
-
# :nodes => [
|
65
|
-
# {
|
66
|
-
# :server_url => "redis://redis.com",
|
67
|
-
# :server_eof => 9,
|
68
|
-
# :server_err => 20,
|
69
|
-
# :info => {
|
70
|
-
# :connections => 10
|
71
|
-
# :used_memory => 1232132
|
72
|
-
# :used_memory_rss => 2323132
|
73
|
-
# :fragmentation => 1.9
|
74
|
-
# :expired_keys => 2132
|
75
|
-
# :evicted_keys => 23223
|
76
|
-
# :hits => 2321
|
77
|
-
# :misses => 234232
|
78
|
-
# :keys => 2121
|
79
|
-
# :max_memory => 123233232
|
80
|
-
# :hit_ratio => 0.9
|
81
|
-
# },
|
82
|
-
# ...
|
83
|
-
# }
|
84
|
-
# ]
|
85
|
-
# :client_eof => 2,
|
86
|
-
# :client_connections => 3,
|
87
|
-
# ...
|
88
|
-
# }
|
89
|
-
# ],
|
90
|
-
# :server_attribute1 => "server_value1",
|
91
|
-
# :server_attribute2 => "server_value2",
|
92
|
-
# }
|
88
|
+
|
89
|
+
# Returns hash with server and node statistics
|
90
|
+
# See example.json @ project root to get details about the structure
|
93
91
|
def overview
|
94
92
|
data = { :clusters => [], :config => config }
|
95
93
|
|
96
94
|
stats.each do |cluster_name, cluster_data|
|
97
|
-
|
98
95
|
# Setting global server attributes ( like hostname, version etc...)
|
99
96
|
unless cluster_data.is_a? Hash
|
100
97
|
data[cluster_name] = cluster_data
|
101
98
|
next
|
102
99
|
end
|
103
|
-
|
104
|
-
#
|
105
|
-
|
100
|
+
|
101
|
+
next unless redis? cluster_name # skip memcached clusters
|
102
|
+
|
103
|
+
aliases = node_aliases cluster_name
|
106
104
|
cluster = { nodes: [], name: cluster_name }
|
107
105
|
cluster_data.each do |node, node_value|
|
108
|
-
|
109
|
-
# Adding cluster Node
|
106
|
+
# Adding node
|
110
107
|
if node_value.kind_of? Hash
|
108
|
+
node_data = cluster_data[node]
|
109
|
+
node = aliases[node] || node
|
111
110
|
url = ( node =~ /redis\:\/\// ) ? node : "redis://#{node}"
|
112
111
|
info = redis_info(url)
|
113
112
|
cluster[:nodes] << {
|
114
113
|
server_url: url, info: info, running: info.any?
|
115
|
-
}.merge(
|
114
|
+
}.merge(node_data)
|
116
115
|
else # Cluster attribute
|
117
116
|
cluster[node] = node_value
|
118
117
|
end
|
119
|
-
|
120
118
|
end
|
121
119
|
data[:clusters].push cluster
|
122
120
|
end
|
123
121
|
data
|
124
122
|
end
|
125
|
-
|
123
|
+
|
124
|
+
# Check if a given cluster name was configure as Redis
|
126
125
|
def redis? cluster
|
127
126
|
config[cluster]["redis"] rescue false
|
128
127
|
end
|
129
|
-
|
128
|
+
|
129
|
+
# https://github.com/twitter/twemproxy/blob/master/notes/recommendation.md#node-names-for-consistent-hashing
|
130
|
+
def node_aliases cluster
|
131
|
+
Hash[config[cluster]["servers"].map(&:split).each {|o| o[0]=o[0].split(":")[0..1].join(":")}.map(&:reverse)]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns hash with information about a given Redis
|
130
135
|
def redis_info url
|
131
136
|
begin
|
132
137
|
redis = Redis.connect(url: url)
|
@@ -153,11 +158,27 @@ module Nutcracker
|
|
153
158
|
}.tap {|d| d['hit_ratio'] = d['hits'].to_f / (d['hits']+d['misses']).to_f if d['hits'] > 0 }
|
154
159
|
end
|
155
160
|
|
161
|
+
# Returns a hash with server statistics
|
156
162
|
def stats
|
157
|
-
JSON.parse TCPSocket.new('
|
163
|
+
JSON.parse TCPSocket.new('127.0.0.1',@options[:stats_port]).read rescue {}
|
158
164
|
end
|
159
165
|
|
160
166
|
private
|
167
|
+
|
168
|
+
def command
|
169
|
+
['-c', @options[:config_file],'-s',@options[:stats_port],*@options[:args]].map(&:to_s)
|
170
|
+
end
|
171
|
+
|
172
|
+
def defaults
|
173
|
+
{ :args => [],
|
174
|
+
:config_file => 'conf/nutcracker.yaml',
|
175
|
+
:stats_port => 22222,
|
176
|
+
:attached => false}
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate options
|
180
|
+
options.tap { File.exists? options[:config_file] or raise "#{options[:config_file]} not found" }
|
181
|
+
end
|
161
182
|
|
162
183
|
def running!
|
163
184
|
running? or raise RuntimeError, "Nutcracker isn't running..."
|
data/lib/nutcracker/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nutcracker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.4.
|
4
|
+
version: 0.2.4.9
|
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: 2013-
|
12
|
+
date: 2013-09-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
description: Gem
|
62
|
+
description: Gem wrapper for Twitter's Nutcracker
|
63
63
|
email: eran@kontera.com
|
64
64
|
executables:
|
65
65
|
- nutcracker
|
@@ -219,7 +219,8 @@ files:
|
|
219
219
|
homepage: http://www.kontera.com
|
220
220
|
licenses: []
|
221
221
|
post_install_message:
|
222
|
-
rdoc_options:
|
222
|
+
rdoc_options:
|
223
|
+
- --no-private --protected lib/**/*.rb - README.md
|
223
224
|
require_paths:
|
224
225
|
- lib
|
225
226
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -236,11 +237,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
237
|
version: '0'
|
237
238
|
segments:
|
238
239
|
- 0
|
239
|
-
hash: -
|
240
|
+
hash: -2381185996366621588
|
240
241
|
requirements: []
|
241
242
|
rubyforge_project: ruby-nutcracker
|
242
243
|
rubygems_version: 1.8.25
|
243
244
|
signing_key:
|
244
245
|
specification_version: 3
|
245
|
-
summary: Twitter's
|
246
|
+
summary: Gem wrapper for Twitter's Nutcracker
|
246
247
|
test_files: []
|