nutcracker 0.2.4.8 → 0.2.4.9

Sign up to get free protection for your applications and to get access to all the features.
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: []