nutcracker 0.2.4.beta3 → 0.2.4.mac9

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