nutcracker 0.2.4.beta3 → 0.2.4.mac9

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
@@ -3,15 +3,15 @@
3
3
 
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
- ### DISCLAIMER
7
- this is still a work in progress...
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.
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.
11
11
 
12
12
  ## Plugins
13
13
  - [nutcracker-graphite](https://github.com/kontera-technologies/nutcracker-graphite) - Send cluster stats to Graphite
14
- - [nutcracker-ui](https://github.com/kontera-technologies/nutcracker-ui) - Web interface for admin operations and graphs
14
+ - [nutcracker-web](https://github.com/kontera-technologies/nutcracker-web) - Web interface
15
15
 
16
16
  ### Installation
17
17
  Add this line to your application's Gemfile:
@@ -68,9 +68,18 @@ nutcracker.start
68
68
  nutcracker.join # wait for server to exit
69
69
  ```
70
70
 
71
- ## Wanna build a new version?
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
+
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
74
80
  * look for `nutcracker-X.Y.Z` gem under the pkg folder
75
81
 
82
+ ### Warranty
83
+ This software is provided “as is” and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose.
84
+
76
85
  > for more details like licensing etc, please look @ [Nutcracker](https://github.com/twitter/twemproxy)
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ task :download do
18
18
  File.open("ext/nutcracker/extconf.rb",'w') do |file|
19
19
  file.puts %q{
20
20
  raise "no support for #{RUBY_PLATFORM}" if RUBY_PLATFORM =~ /darwin|mswin|mingw/
21
- system './configure'
21
+ system "./configure --prefix=#{File.expand_path('..',__FILE__)}"
22
22
  system 'make'
23
23
  }
24
24
  end
@@ -0,0 +1,3 @@
1
+ system "./configure --prefix=#{File.expand_path('..',__FILE__)}"
2
+ system 'make'
3
+
@@ -2,65 +2,183 @@ require 'nutcracker/version'
2
2
  require 'socket'
3
3
  require 'json'
4
4
  require 'yaml'
5
+ require 'redis'
5
6
 
6
7
  module Nutcracker
7
-
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'
8
12
  def self.start options
9
13
  Nutcracker::Wrapper.new(options).start
10
14
  end
11
-
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
12
25
  def self.executable
13
26
  File.expand_path("../../ext/nutcracker/src/nutcracker", __FILE__)
14
27
  end
15
28
 
29
+ # Returns the version string
16
30
  def self.version
17
31
  Nutcracker::VERSION
18
32
  end
19
33
 
20
34
  class Wrapper
21
- attr_reader :pid, :config_file
35
+ attr_reader :pid
22
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
23
42
  def initialize options
24
- @config_file = options.fetch :config_file
43
+ @options = validate defaults.merge options
25
44
  end
26
-
27
- def start
28
- return if running?
29
- @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
30
50
  Kernel.at_exit { kill if running? }
31
51
  self
32
52
  end
33
-
53
+
54
+ # Returns the current running status
34
55
  def running?
35
- !!(pid and ::Process.getpgid pid rescue false)
56
+ attached? ? stats.any? : !!(pid and ::Process.getpgid pid rescue false)
36
57
  end
37
-
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
38
65
  def stop
39
66
  sig :TERM
40
67
  end
41
-
68
+
69
+ # Kills the Nutcracker service
42
70
  def kill
43
71
  sig :KILL
44
72
  end
45
-
73
+
74
+ # Wait for the process to exit
46
75
  def join
47
- running! and ::Process.waitpid2 pid
48
- end
49
-
50
- def stats
51
- JSON.parse TCPSocket.new('localhost',22222).read rescue {}
76
+ attached? ? sleep : (running! and ::Process.waitpid2 pid)
52
77
  end
53
78
 
79
+ # Returns Nutcracker's configuration hash
54
80
  def config
55
- @config ||= YAML.load_file config_file
81
+ @config ||= YAML.load_file @options[:config_file]
56
82
  end
57
83
 
58
- # syntactic sugar for initialize plugins
84
+ # Syntactic sugar for initialize plugins
59
85
  def use plugin, *args
60
- Nutcracker.const_get("#{plugin.to_s.capitalize}").start(self,*args)
86
+ Nutcracker.const_get(plugin.to_s.capitalize).start(self,*args)
87
+ end
88
+
89
+ # Returns hash with server and node statistics
90
+ # See example.json @ project root to get details about the structure
91
+ def overview
92
+ data = { :clusters => [], :config => config }
93
+
94
+ stats.each do |cluster_name, cluster_data|
95
+ # Setting global server attributes ( like hostname, version etc...)
96
+ unless cluster_data.is_a? Hash
97
+ data[cluster_name] = cluster_data
98
+ next
99
+ end
100
+
101
+ next unless redis? cluster_name # skip memcached clusters
102
+
103
+ aliases = node_aliases cluster_name
104
+ cluster = { nodes: [], name: cluster_name }
105
+ cluster_data.each do |node, node_value|
106
+ # Adding node
107
+ if node_value.kind_of? Hash
108
+ node_data = cluster_data[node]
109
+ node = aliases[node] || node
110
+ url = ( node =~ /redis\:\/\// ) ? node : "redis://#{node}"
111
+ info = redis_info(url)
112
+ cluster[:nodes] << {
113
+ server_url: url, info: info, running: info.any?
114
+ }.merge(node_data)
115
+ else # Cluster attribute
116
+ cluster[node] = node_value
117
+ end
118
+ end
119
+ data[:clusters].push cluster
120
+ end
121
+ data
122
+ end
123
+
124
+ # Check if a given cluster name was configure as Redis
125
+ def redis? cluster
126
+ config[cluster]["redis"] rescue false
127
+ end
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
135
+ def redis_info url
136
+ begin
137
+ redis = Redis.connect(url: url)
138
+ info = redis.info
139
+ db_size = redis.dbsize
140
+ max_memory = redis.config(:get, 'maxmemory')['maxmemory'].to_i
141
+ redis.quit
142
+ rescue Exception
143
+ return {}
144
+ end
145
+
146
+ {
147
+ 'connections' => info['connected_clients'].to_i,
148
+ 'used_memory' => info['used_memory'].to_f,
149
+ 'used_memory_rss' => info['used_memory_rss'].to_f,
150
+ 'fragmentation' => info['mem_fragmentation_ratio'].to_f,
151
+ 'expired_keys' => info['expired_keys'].to_i,
152
+ 'evicted_keys' => info['evicted_keys'].to_i,
153
+ 'hits' => info['keyspace_hits'].to_i,
154
+ 'misses' => info['keyspace_misses'].to_i,
155
+ 'keys' => db_size,
156
+ 'max_memory' => max_memory,
157
+ 'hit_ratio' => 0
158
+ }.tap {|d| d['hit_ratio'] = d['hits'].to_f / (d['hits']+d['misses']).to_f if d['hits'] > 0 }
159
+ end
160
+
161
+ # Returns a hash with server statistics
162
+ def stats
163
+ JSON.parse TCPSocket.new('127.0.0.1',@options[:stats_port]).read rescue {}
61
164
  end
62
165
 
63
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
64
182
 
65
183
  def running!
66
184
  running? or raise RuntimeError, "Nutcracker isn't running..."
@@ -1,3 +1,3 @@
1
1
  module Nutcracker
2
- VERSION = "0.2.4.beta3"
2
+ VERSION = "0.2.4.mac9"
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.beta3
4
+ version: 0.2.4.mac9
5
5
  prerelease: 6
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-05-26 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
@@ -43,11 +43,28 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: 0.14.0
46
- description: Gem/Bundler benefits for Twitter's Nutcraker C app
46
+ - !ruby/object:Gem::Dependency
47
+ name: redis
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Gem wrapper for Twitter's Nutcracker
47
63
  email: eran@kontera.com
48
64
  executables:
49
65
  - nutcracker
50
- extensions: []
66
+ extensions:
67
+ - ext/nutcracker/extconf.rb
51
68
  extra_rdoc_files: []
52
69
  files:
53
70
  - README.md
@@ -398,7 +415,8 @@ files:
398
415
  homepage: http://www.kontera.com
399
416
  licenses: []
400
417
  post_install_message:
401
- rdoc_options: []
418
+ rdoc_options:
419
+ - --no-private --protected lib/**/*.rb - README.md
402
420
  require_paths:
403
421
  - lib
404
422
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -418,5 +436,5 @@ rubyforge_project: ruby-nutcracker
418
436
  rubygems_version: 1.8.25
419
437
  signing_key:
420
438
  specification_version: 3
421
- summary: Twitter's Nutcraker Gem Wrapper
439
+ summary: Gem wrapper for Twitter's Nutcracker
422
440
  test_files: []