Class: Nutcracker::Wrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/nutcracker.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Wrapper

Initialize a new Nutcracker process wrappper

Options Hash (options):

  • :config_file (String) — default: conf/nutcracker.yaml

    path to nutcracker’s configuration file

  • :stats_uri (String)

    Nutcracker stats URI - tcp://localhost:22222

  • :max_memory (String)

    use fixed max memory size ( ignore server configuration )

  • :args (Array) — default: []

    array with additional command line arguments



45
46
47
48
# File 'lib/nutcracker.rb', line 45

def initialize options
  @options = validate defaults.merge options
  @options[:stats_uri] = URI @options[:stats_uri]
end

Instance Attribute Details

#pidObject (readonly)

Returns the value of attribute pid.



37
38
39
# File 'lib/nutcracker.rb', line 37

def pid
  @pid
end

Instance Method Details

#attached?Boolean

Returns true if the current instance was initialize with the attached flag



67
68
69
# File 'lib/nutcracker.rb', line 67

def attached?
  @options[:attached]
end

#configObject

Returns Nutcracker’s configuration hash



87
88
89
# File 'lib/nutcracker.rb', line 87

def config
  @config ||= YAML.load_file @options[:config_file]
end

#joinObject

Wait for the process to exit



82
83
84
# File 'lib/nutcracker.rb', line 82

def join
  attached? ? sleep : (running! and ::Process.waitpid2 pid rescue nil)
end

#killObject

Kills the Nutcracker service



77
78
79
# File 'lib/nutcracker.rb', line 77

def kill
  sig :KILL
end

#node_aliases(cluster) ⇒ Object



137
138
139
# File 'lib/nutcracker.rb', line 137

def node_aliases cluster
  Hash[config[cluster]["servers"].map(&:split).each {|o| o[0]=o[0].split(":")[0..1].join(":")}.map(&:reverse)]
end

#overviewObject

Returns hash with server and node statistics See example.json @ project root to get details about the structure



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/nutcracker.rb', line 98

def overview
  data = { :clusters => [], :config => config }

  stats.each do |cluster_name, cluster_data|
    # Setting global server attributes ( like hostname, version etc...)
    unless cluster_data.is_a? Hash
      data[cluster_name] = cluster_data
      next
    end

    #next unless redis? cluster_name # skip memcached clusters

    aliases = node_aliases cluster_name
    cluster = { nodes: [], name: cluster_name }
    cluster_data.each do |node, node_value|
      # Adding node
      if node_value.kind_of? Hash
        node_data = cluster_data[node]
        node = aliases[node] || node
        url = ( node =~ /redis\:\/\// ) ? node : "redis://#{node}"
        info = redis_info(url, config[cluster_name]["redis_auth"])
        cluster[:nodes] << {
          server_url: url, info: info, running: info.any?
        }.merge(node_data)
      else # Cluster attribute
        cluster[node] = node_value
      end
    end
    data[:clusters].push cluster
  end
  data
end

#redis?(cluster) ⇒ Boolean

Check if a given cluster name was configure as Redis



132
133
134
# File 'lib/nutcracker.rb', line 132

def redis? cluster
  config[cluster]["redis"] rescue false
end

#redis_info(url, password) ⇒ Object

Returns hash with information about a given Redis



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/nutcracker.rb', line 142

def redis_info url, password
  begin
    r = Redis.new url: url, password: password
    info = r.info.merge 'dbsize' => r.dbsize
  rescue Exception => e
    STDERR.puts "[ERROR][#{__FILE__}:#{__LINE__}] Failed to get data from Redis - " +
      "#{url.inspect} (using password #{password.inspect}): #{e.message}\n#{e.backtrace.join("\n")}"
    return {}
  end

  begin
    info['maxmemory'] = @options.fetch(:max_memory) { r.config(:get, 'maxmemory')['maxmemory'] }
  rescue Exception
    info['maxmemory'] = info['used_memory_rss']
  end

  r.quit

  {
    'connections'     => info['connected_clients'].to_i,
    'used_memory'     => info['used_memory'].to_f,
    'used_memory_rss' => info['used_memory_rss'].to_f,
    'fragmentation'   => info['mem_fragmentation_ratio'].to_f,
    'expired_keys'    => info['expired_keys'].to_i,
    'evicted_keys'    => info['evicted_keys'].to_i,
    'hits'            => info['keyspace_hits'].to_i,
    'misses'          => info['keyspace_misses'].to_i,
    'keys'            => info['dbsize'].to_i,
    'max_memory'      => info['maxmemory'].to_i,
    'hit_ratio'       => 0
  }.tap {|d| d['hit_ratio'] = d['hits'].to_f / (d['hits']+d['misses']).to_f if d['hits'] > 0 }
end

#running?Boolean

Returns the current running status



62
63
64
# File 'lib/nutcracker.rb', line 62

def running?
  attached? ? stats.any? : !!(pid and ::Process.getpgid pid rescue nil)
end

#start(*args) ⇒ Object

launching the Nutcracker service



51
52
53
54
55
56
57
58
59
# File 'lib/nutcracker.rb', line 51

def start *args
  return self if attached? or running?
  @pid = ::Process.spawn Nutcracker.executable, *command
  Process.detach(@pid)
  sleep 2
  raise "Nutcracker failed to start" unless running?
  Kernel.at_exit { kill if running? }
  self
end

#statsObject

Returns a hash with server statistics



176
177
178
# File 'lib/nutcracker.rb', line 176

def stats
  JSON.parse TCPSocket.new(@options[:stats_uri].host,@options[:stats_uri].port).read rescue {}
end

#stopObject

Stops the Nutcracker service



72
73
74
# File 'lib/nutcracker.rb', line 72

def stop
  sig :TERM
end

#use(plugin, *args) ⇒ Object

Syntactic sugar for initialize plugins



92
93
94
# File 'lib/nutcracker.rb', line 92

def use plugin, *args
  Nutcracker.const_get(plugin.to_s.capitalize).start(self,*args)
end