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 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, so things could be a little buggy, if you find one, feel free to [report](https://github.com/kontera-technologies/nutcracker/issues) it.
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, :config_file
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
- @config_file = options.fetch :config_file
43
+ @options = validate defaults.merge options
26
44
  end
27
-
28
- def start
29
- return if running?
30
- @pid = ::Process.spawn Nutcracker.executable, '-c', config_file
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
- # syntactic sugar for initialize plugins
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
- # Different structure to stats and with more infomation from Redis.info
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
- # Adding cluster
105
- next unless redis? cluster_name # only support redis clusters
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(cluster_data[node])
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('localhost',22222).read rescue {}
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..."
@@ -1,3 +1,3 @@
1
1
  module Nutcracker
2
- VERSION = "0.2.4.8"
2
+ VERSION = "0.2.4.9"
3
3
  end
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.8
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-07-02 00:00:00.000000000 Z
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/Bundler benefits for Twitter's Nutcraker C app
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: -573202003723177898
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 Nutcraker Gem Wrapper
246
+ summary: Gem wrapper for Twitter's Nutcracker
246
247
  test_files: []