flare-tools 0.1.4 → 0.4.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Flare-tools.txt +0 -0
- data/History.txt +114 -2
- data/LICENSE +21 -0
- data/Manifest.txt +65 -8
- data/README.txt +356 -0
- data/Rakefile +90 -25
- data/Tutorial.txt +370 -0
- data/bin/flare-admin +6 -0
- data/bin/flare-argv0 +6 -0
- data/bin/flare-deploy +6 -0
- data/bin/flare-keychecker +6 -0
- data/bin/flare-part +6 -0
- data/bin/flare-ping +6 -0
- data/bin/flare-stats +4 -10
- data/bin/flare-zkadmin +6 -0
- data/lib/flare/net/connection.rb +98 -0
- data/lib/flare/test/cluster.rb +140 -0
- data/lib/flare/test/daemon.rb +144 -0
- data/lib/flare/test/node.rb +62 -0
- data/lib/flare/tools.rb +18 -16
- data/lib/flare/tools/cli.rb +32 -0
- data/lib/flare/tools/cli/activate.rb +106 -0
- data/lib/flare/tools/cli/balance.rb +83 -0
- data/lib/flare/tools/cli/cli_util.rb +77 -0
- data/lib/flare/tools/cli/deploy.rb +170 -0
- data/lib/flare/tools/cli/down.rb +85 -0
- data/lib/flare/tools/cli/dump.rb +219 -0
- data/lib/flare/tools/cli/dumpkey.rb +117 -0
- data/lib/flare/tools/cli/flare_admin.rb +81 -0
- data/lib/flare/tools/cli/flare_argv0.rb +60 -0
- data/lib/flare/tools/cli/flare_keychecker.rb +106 -0
- data/lib/flare/tools/cli/flare_zkadmin.rb +226 -0
- data/lib/flare/tools/cli/index.rb +54 -0
- data/lib/flare/tools/cli/list.rb +93 -0
- data/lib/flare/tools/cli/master.rb +143 -0
- data/lib/flare/tools/cli/part.rb +100 -0
- data/lib/flare/tools/cli/ping.rb +81 -0
- data/lib/flare/tools/cli/reconstruct.rb +164 -0
- data/lib/flare/tools/cli/remove.rb +119 -0
- data/lib/flare/tools/cli/restore.rb +180 -0
- data/lib/flare/tools/cli/slave.rb +125 -0
- data/lib/flare/tools/cli/stats.rb +229 -122
- data/lib/flare/tools/cli/sub_command.rb +73 -0
- data/lib/flare/tools/cli/summary.rb +97 -0
- data/lib/flare/tools/cli/threads.rb +78 -0
- data/lib/flare/tools/cli/verify.rb +202 -0
- data/lib/flare/tools/client.rb +267 -0
- data/lib/flare/tools/cluster.rb +319 -0
- data/lib/flare/tools/common.rb +196 -0
- data/lib/flare/tools/index_server.rb +51 -0
- data/lib/flare/tools/node.rb +162 -0
- data/lib/flare/tools/stats.rb +75 -0
- data/lib/flare/tools/zk_util.rb +28 -0
- data/lib/flare/util.rb +34 -0
- data/lib/flare/util/bwlimit.rb +132 -0
- data/lib/flare/util/command_line.rb +79 -0
- data/lib/flare/util/conf.rb +71 -0
- data/lib/flare/util/constant.rb +25 -0
- data/lib/flare/util/conversion.rb +26 -0
- data/lib/flare/util/default_logger.rb +52 -0
- data/lib/flare/util/exception.rb +19 -0
- data/lib/flare/util/filesystem.rb +30 -0
- data/lib/flare/util/flared_conf.rb +33 -0
- data/lib/flare/util/flarei_conf.rb +32 -0
- data/lib/flare/util/hash_function.rb +32 -0
- data/lib/flare/util/interruption.rb +70 -0
- data/lib/flare/util/key_resolver.rb +67 -0
- data/lib/flare/util/log4r_logger.rb +79 -0
- data/lib/flare/util/logger.rb +40 -0
- data/lib/flare/util/logging.rb +84 -0
- data/lib/flare/util/result.rb +53 -0
- data/test/test/experimental/cache_test.rb +113 -0
- data/test/test/experimental/key_distribution_test.rb +38 -0
- data/test/test/experimental/keychecker_test.rb +60 -0
- data/test/test/experimental/list_test.rb +108 -0
- data/test/test/extra/replication_test.rb +184 -0
- data/test/test/integration/cli_test.rb +348 -0
- data/test/test/integration/dump_expired_test.rb +103 -0
- data/test/test/integration/dump_test.rb +128 -0
- data/test/test/integration/index_server_test.rb +35 -0
- data/test/test/integration/node_test.rb +78 -0
- data/test/test/integration/partition_test.rb +235 -0
- data/test/test/integration/proxy_test.rb +54 -0
- data/test/test/integration/stats_test.rb +79 -0
- data/test/test/system/flare_admin_test.rb +191 -0
- data/test/test/unit/bwlimit_test.rb +52 -0
- data/test/test/unit/cluster_test.rb +96 -0
- data/test/test/unit/daemon_test.rb +30 -0
- data/test/test/unit/logger_test.rb +46 -0
- data/test/test/unit/tools_test.rb +25 -0
- data/test/test/unit/util_test.rb +70 -0
- metadata +239 -84
- data/README.rdoc +0 -83
- data/bin/flare-partition-setting +0 -12
- data/lib/flare/tools/cli/partition_setting.rb +0 -86
- data/lib/flare/tools/core.rb +0 -189
- data/lib/flare/tools/logger.rb +0 -31
- data/test/test_flare-tools.rb +0 -11
- data/test/test_helper.rb +0 -3
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/tools/index_server'
|
7
|
+
require 'flare/util/conversion'
|
8
|
+
require 'flare/tools/common'
|
9
|
+
require 'flare/tools/cli/sub_command'
|
10
|
+
|
11
|
+
#
|
12
|
+
module Flare
|
13
|
+
module Tools
|
14
|
+
module Cli
|
15
|
+
|
16
|
+
# == Description
|
17
|
+
#
|
18
|
+
class Remove < SubCommand
|
19
|
+
include Flare::Util::Conversion
|
20
|
+
include Flare::Tools::Common
|
21
|
+
|
22
|
+
myname :remove
|
23
|
+
desc "remove a node. (experimental)"
|
24
|
+
usage "remove"
|
25
|
+
|
26
|
+
def setup(opt)
|
27
|
+
opt.on('--force', "commit changes without confirmation") {@force = true}
|
28
|
+
opt.on('--wait=SECOND', "specify the time to wait node for getting ready (default:#{@wait})") {|v| @wait = v.to_i}
|
29
|
+
opt.on('--retry=COUNT', "retry count(default:#{@retry})") {|v| @retry = v.to_i}
|
30
|
+
opt.on('--connection-threshold=[COUNT]', "specify connection threashold (default:#{@connection_threshold})") {|v| @connection_threshold = v.to_i}
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
super
|
35
|
+
@force = false
|
36
|
+
@wait = 30
|
37
|
+
@retry = 5
|
38
|
+
@connection_threshold = 2
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(config, *args)
|
42
|
+
hosts = args.map {|x| x.split(':')}
|
43
|
+
hosts.each do |x|
|
44
|
+
if x.size != 2
|
45
|
+
error "invalid argument '#{x.join(':')}'. it must be hostname:port."
|
46
|
+
return S_NG
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout]) do |s|
|
51
|
+
cluster = fetch_cluster(s)
|
52
|
+
|
53
|
+
hosts.each do |hostname,port|
|
54
|
+
nodekey = nodekey_of hostname, port
|
55
|
+
unless cluster.has_nodekey? nodekey
|
56
|
+
error "unknown node name: #{nodekey}"
|
57
|
+
return S_NG
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
hosts.each do |hostname,port|
|
62
|
+
exec = false
|
63
|
+
Flare::Tools::Node.open(hostname, port, config[:timeout]) do |n|
|
64
|
+
nwait = @wait
|
65
|
+
node = n.stats
|
66
|
+
cluster = Flare::Tools::Cluster.new(s.host, s.port, s.stats_nodes)
|
67
|
+
while nwait > 0
|
68
|
+
conn = node['curr_connections'].to_i
|
69
|
+
cluster = fetch_cluster(s)
|
70
|
+
role = cluster.node_stat("#{hostname}:#{port}")['role']
|
71
|
+
info "waiting until #{hostname}:#{port} (role=#{role}, connections=#{conn}) is inactive..."
|
72
|
+
if conn <= @connection_threshold && role == 'proxy'
|
73
|
+
exec = true
|
74
|
+
break
|
75
|
+
end
|
76
|
+
interruptible {sleep 1}
|
77
|
+
nwait -= 1
|
78
|
+
node = n.stats
|
79
|
+
end
|
80
|
+
unless @force
|
81
|
+
node_stat = cluster.node_stat("#{hostname}:#{port}")
|
82
|
+
role = node_stat['role']
|
83
|
+
state = node_stat['state']
|
84
|
+
STDERR.print "please shutdown the daemon and continue (node=#{hostname}:#{port}, role=#{role}, state=#{state}) (y/n): "
|
85
|
+
interruptible {
|
86
|
+
exec = false if gets.chomp.upcase != "Y"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if exec
|
92
|
+
suc = false
|
93
|
+
nretry = @retry
|
94
|
+
while nretry > 0
|
95
|
+
resp = false
|
96
|
+
info "removing #{hostname}:#{port}."
|
97
|
+
resp = s.node_remove(hostname, port) unless config[:dry_run]
|
98
|
+
if resp
|
99
|
+
suc = true
|
100
|
+
break
|
101
|
+
end
|
102
|
+
nretry -= 1
|
103
|
+
end
|
104
|
+
info "done." if suc
|
105
|
+
info "failed." unless suc
|
106
|
+
else
|
107
|
+
info "skipped."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
puts string_of_nodelist(s.stats_nodes)
|
111
|
+
end
|
112
|
+
|
113
|
+
S_OK
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/tools/node'
|
7
|
+
require 'flare/tools/index_server'
|
8
|
+
require 'flare/tools/common'
|
9
|
+
require 'flare/util/conversion'
|
10
|
+
require 'flare/util/constant'
|
11
|
+
require 'flare/tools/cli/sub_command'
|
12
|
+
require 'csv'
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'tokyocabinet'
|
16
|
+
rescue LoadError => e
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
module Flare
|
21
|
+
module Tools
|
22
|
+
module Cli
|
23
|
+
|
24
|
+
class Restore < SubCommand
|
25
|
+
|
26
|
+
class Restorer
|
27
|
+
attr_reader :name
|
28
|
+
def iterate &block
|
29
|
+
raise "internal error"
|
30
|
+
end
|
31
|
+
def close
|
32
|
+
raise "internal error"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TchRestorer < Restorer
|
37
|
+
# uint32_t flag -> L // uint32_t
|
38
|
+
# time_t expire -> Q // unsigned long
|
39
|
+
# uint64_t size -> Q // uint64_t
|
40
|
+
# uint64_t version -> Q // uint64_t
|
41
|
+
# uint32_t option -> L // uint32_t
|
42
|
+
def self.myname
|
43
|
+
"tch"
|
44
|
+
end
|
45
|
+
def initialize filepath
|
46
|
+
raise "output file not specified." if filepath.nil?
|
47
|
+
raise "#{filepath} isn't a path." unless filepath.kind_of?(String)
|
48
|
+
@hdb = TokyoCabinet::HDB.new
|
49
|
+
@hdb.open(filepath, TokyoCabinet::HDB::OCREAT|TokyoCabinet::HDB::OREADER)
|
50
|
+
end
|
51
|
+
def iterate &block
|
52
|
+
@hdb.iterinit
|
53
|
+
while (key = @hdb.iternext)
|
54
|
+
value = @hdb.get(key)
|
55
|
+
a = value.unpack("LQQQC*")
|
56
|
+
flag, expire, size, version = a.shift(4)
|
57
|
+
data = a.pack("C*")
|
58
|
+
block.call(key, data, flag, expire)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
def close
|
62
|
+
@hdb.close
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Restorers = []
|
67
|
+
Restorers << TchRestorer if defined? TokyoCabinet
|
68
|
+
Formats = Restorers.map {|n| n.myname}
|
69
|
+
|
70
|
+
include Flare::Util::Conversion
|
71
|
+
include Flare::Util::Constant
|
72
|
+
include Flare::Tools::Common
|
73
|
+
|
74
|
+
myname :restore
|
75
|
+
desc "restore data to nodes. (experimental)"
|
76
|
+
usage "restore [hostname:port]"
|
77
|
+
|
78
|
+
def setup(opt)
|
79
|
+
opt.on('-i', '--input=FILE', "input from file") {|v| @input = v}
|
80
|
+
opt.on('-f', '--format=FORMAT', "input format [#{Formats.join(',')}]") {|v|
|
81
|
+
@format = v
|
82
|
+
}
|
83
|
+
opt.on('--bwlimit=BANDWIDTH', "bandwidth limit (bps)") {|v| @bwlimit = v}
|
84
|
+
opt.on('--include=PATTERN', "include pattern") {|v|
|
85
|
+
begin
|
86
|
+
@include = Regexp.new(v)
|
87
|
+
rescue RegexpError => e
|
88
|
+
raise "#{v} isn't a valid regular expression."
|
89
|
+
end
|
90
|
+
}
|
91
|
+
opt.on('--prefix-include=STRING', "prefix string") {|v|
|
92
|
+
@prefix_include = Regexp.new("^"+Regexp.escape(v))
|
93
|
+
}
|
94
|
+
opt.on('--exclude=PATTERN', "exclude pattern") {|v|
|
95
|
+
begin
|
96
|
+
@exclude = Regexp.new(v)
|
97
|
+
rescue RegexpError => e
|
98
|
+
raise "#{v} isn't a valid regular expression."
|
99
|
+
end
|
100
|
+
}
|
101
|
+
opt.on('--print-keys', "enables key dump to console") {@print_key = true}
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
super
|
106
|
+
@input = nil
|
107
|
+
@format = nil
|
108
|
+
@wait = 0
|
109
|
+
@part = 0
|
110
|
+
@partsize = 1
|
111
|
+
@bwlimit = 0
|
112
|
+
@include = nil
|
113
|
+
@prefix_include = nil
|
114
|
+
@exclude = nil
|
115
|
+
@print_key = false
|
116
|
+
end
|
117
|
+
|
118
|
+
def execute(config, *args)
|
119
|
+
STDERR.puts "please install tokyocabinet via gem command." unless defined? TokyoCabinet
|
120
|
+
|
121
|
+
dry_run = config[:dry_run]
|
122
|
+
|
123
|
+
unless @format.nil? || Formats.include?(@format)
|
124
|
+
STDERR.puts "unknown format: #{@format}"
|
125
|
+
return S_NG
|
126
|
+
end
|
127
|
+
|
128
|
+
if @prefix_include
|
129
|
+
if @include
|
130
|
+
STDERR.puts "--include option is specified."
|
131
|
+
return S_NG
|
132
|
+
end
|
133
|
+
@include = @prefix_include
|
134
|
+
end
|
135
|
+
|
136
|
+
hosts = args.map {|x| x.split(':')}
|
137
|
+
hosts.each do |x|
|
138
|
+
if x.size != 2
|
139
|
+
STDERR.puts "invalid argument '#{x.join(':')}'."
|
140
|
+
return S_NG
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
restorer = case @format
|
145
|
+
when TchRestorer.myname
|
146
|
+
TchRestorer.new(@input)
|
147
|
+
else
|
148
|
+
raise "invalid format"
|
149
|
+
end
|
150
|
+
|
151
|
+
nodes = hosts.map do |hostname,port|
|
152
|
+
Flare::Tools::Node.open(hostname, port.to_i, config[:timeout], @bwlimit, @bwlimit)
|
153
|
+
end
|
154
|
+
|
155
|
+
count = 0
|
156
|
+
restorer.iterate do |key,data,flag,expire|
|
157
|
+
if @include.nil? || @include =~ key
|
158
|
+
next if @exclude && @exclude =~ key
|
159
|
+
STDOUT.puts key if @print_key
|
160
|
+
nodes[0].set(key, data, flag, expire) unless dry_run
|
161
|
+
count += 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
STDERR.puts "#{count} entries have been restored."
|
165
|
+
|
166
|
+
nodes.each do |n|
|
167
|
+
n.close
|
168
|
+
end
|
169
|
+
|
170
|
+
restorer.close
|
171
|
+
|
172
|
+
S_OK
|
173
|
+
end # execute()
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- coding: utf-8; -*-
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
5
|
+
|
6
|
+
require 'flare/tools/stats'
|
7
|
+
require 'flare/tools/node'
|
8
|
+
require 'flare/tools/index_server'
|
9
|
+
require 'flare/tools/cluster'
|
10
|
+
require 'flare/tools/common'
|
11
|
+
require 'flare/util/conversion'
|
12
|
+
require 'flare/util/constant'
|
13
|
+
require 'flare/tools/cli/sub_command'
|
14
|
+
|
15
|
+
module Flare
|
16
|
+
module Tools
|
17
|
+
module Cli
|
18
|
+
class Slave < SubCommand
|
19
|
+
include Flare::Util::Conversion
|
20
|
+
include Flare::Util::Constant
|
21
|
+
include Flare::Tools::Common
|
22
|
+
|
23
|
+
DefaultRetry = 10
|
24
|
+
|
25
|
+
myname :slave
|
26
|
+
desc "construct slaves from proxy nodes."
|
27
|
+
usage "slave [hostname:port:balance:partition] ..."
|
28
|
+
|
29
|
+
def setup(opt)
|
30
|
+
opt.on('--force', "commit changes without confirmation") {@force = true}
|
31
|
+
opt.on('--retry=COUNT', "specify retry count(default:#{@retry})") {|v| @retry = v.to_i}
|
32
|
+
opt.on('--clean', "clear datastore before construction") {@clean = true}
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
super
|
37
|
+
@force = false
|
38
|
+
@retry = DefaultRetry
|
39
|
+
@clean = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute(config, *args)
|
43
|
+
return S_NG if args.empty?
|
44
|
+
|
45
|
+
hosts = args.map do |arg|
|
46
|
+
hostname, port, balance, partition, rest = arg.split(':', 5)
|
47
|
+
if rest != nil || balance.nil?
|
48
|
+
error "invalid argument '#{arg}'. it must be hostname:port:balance:partition."
|
49
|
+
return S_NG
|
50
|
+
end
|
51
|
+
[hostname, port, balance.to_i, partition.to_i]
|
52
|
+
end
|
53
|
+
|
54
|
+
Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout]) do |s|
|
55
|
+
cluster = fetch_cluster(s)
|
56
|
+
|
57
|
+
hosts.each do |hostname,port,balance,partition|
|
58
|
+
nodekey = nodekey_of hostname, port
|
59
|
+
|
60
|
+
unless cluster.has_nodekey? nodekey
|
61
|
+
error "invalid 'hostname:port' pair: #{nodekey}"
|
62
|
+
return S_NG
|
63
|
+
end
|
64
|
+
|
65
|
+
node = cluster.node_stat(nodekey)
|
66
|
+
|
67
|
+
if node['role'] != 'proxy'
|
68
|
+
puts "#{nodekey} is not a proxy."
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
exec = @force
|
73
|
+
unless exec
|
74
|
+
STDERR.print "making node slave (node=#{nodekey}, role=#{node['role']} -> slave) (y/n): "
|
75
|
+
interruptible do
|
76
|
+
exec = true if gets.chomp.upcase == "Y"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
if exec && !config[:dry_run]
|
80
|
+
if @clean
|
81
|
+
Flare::Tools::Node.open(hostname, port, config[:timeout]) do |n|
|
82
|
+
n.flush_all
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
nretry = 0
|
87
|
+
resp = false
|
88
|
+
while resp == false && nretry < @retry
|
89
|
+
resp = s.set_role(hostname, port, 'slave', 0, partition)
|
90
|
+
if resp
|
91
|
+
puts "started constructing slave node..."
|
92
|
+
else
|
93
|
+
nretry += 1
|
94
|
+
puts "waiting #{nretry} sec..."
|
95
|
+
sleep nretry
|
96
|
+
puts "retrying..."
|
97
|
+
end
|
98
|
+
end
|
99
|
+
if resp
|
100
|
+
wait_for_slave_construction(s, nodekey, config[:timeout])
|
101
|
+
if balance > 0
|
102
|
+
unless @force
|
103
|
+
STDERR.print "changing node's balance (node=#{nodekey}, balance=0 -> #{balance}) (y/n): "
|
104
|
+
exec = interruptible {(gets.chomp.upcase == "Y")}
|
105
|
+
end
|
106
|
+
if exec
|
107
|
+
s.set_role(hostname, port, 'slave', balance, partition)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
else
|
111
|
+
error "failed to change the state."
|
112
|
+
return S_NG
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
STDOUT.puts string_of_nodelist(s.stats_nodes, hosts.map {|x| x[0..1].join(':')})
|
117
|
+
end
|
118
|
+
|
119
|
+
return S_OK
|
120
|
+
end # execute()
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,130 +1,237 @@
|
|
1
1
|
# -*- coding: utf-8; -*-
|
2
|
-
#
|
3
|
-
# Copyright:: Copyright (
|
4
|
-
# License::
|
5
|
-
#
|
6
|
-
# $--- flare-stats - [ by Ruby ] $
|
7
|
-
# vim: foldmethod=marker tabstop=2 shiftwidth=2
|
8
|
-
require 'flare/tools'
|
9
|
-
|
10
|
-
module FlareTools
|
11
|
-
class Stats < Core
|
12
|
-
# {{{ constractor
|
13
|
-
def initialize()
|
14
|
-
# {{{ @format
|
15
|
-
@format = "%20.20s:%5.5s" # hostname:port
|
16
|
-
@format += " %6s" # state
|
17
|
-
@format += " %6s" # role
|
18
|
-
@format += " %9s" # partition
|
19
|
-
@format += " %7s" # balance
|
20
|
-
@format += " %8.8s" # items
|
21
|
-
@format += " %4s" # connection
|
22
|
-
@format += " %6.6s" # behind
|
23
|
-
@format += " %3.3s" # hit
|
24
|
-
@format += " %4.4s" # size
|
25
|
-
@format += " %6.6s" # uptime
|
26
|
-
@format += " %7s" # version
|
27
|
-
@format += "\n"
|
28
|
-
# }}}
|
29
|
-
# {{{ @label
|
30
|
-
@label = @format % [
|
31
|
-
"hostname",
|
32
|
-
"port",
|
33
|
-
"state",
|
34
|
-
"role",
|
35
|
-
"partition",
|
36
|
-
"balance",
|
37
|
-
"items",
|
38
|
-
"conn",
|
39
|
-
"behind",
|
40
|
-
"hit",
|
41
|
-
"size",
|
42
|
-
"uptime",
|
43
|
-
"version",
|
44
|
-
]
|
45
|
-
# }}}
|
46
|
-
super
|
47
|
-
end
|
48
|
-
# }}}
|
49
|
-
# {{{ option_on
|
50
|
-
def option_on
|
51
|
-
super
|
52
|
-
@option.on( '--index-server=[HOSTNAME]', "index server hostname(default:#{@index_server_hostname})") {|v| @index_server_hostname = v}
|
53
|
-
@option.on( '--index-server-port=[PORT]', "index server port(default:#{@index_server_port})") {|v| @index_server_port = v.to_i}
|
54
|
-
end
|
55
|
-
# }}}
|
56
|
-
# {{{ sort_node
|
57
|
-
def sort_node(nodes)
|
58
|
-
# sort partition role hostname
|
59
|
-
nodes.sort_by{|key, val| [val['partition'], val['role'], key]}
|
60
|
-
end
|
61
|
-
# }}}
|
62
|
-
# {{{ str_date
|
63
|
-
def str_date(date)
|
64
|
-
date = date.to_i
|
65
|
-
res = ""
|
66
|
-
# sec
|
67
|
-
if date >= 60
|
68
|
-
date = date / 60
|
69
|
-
else
|
70
|
-
return "#{date}s"
|
71
|
-
end
|
2
|
+
# Authors:: Kiyoshi Ikehara <kiyoshi.ikehara@gree.net>
|
3
|
+
# Copyright:: Copyright (C) GREE, Inc. 2011.
|
4
|
+
# License:: MIT-style
|
72
5
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
6
|
+
require 'thread'
|
7
|
+
require 'flare/tools/index_server'
|
8
|
+
require 'flare/tools/cli/sub_command'
|
9
|
+
require 'flare/tools/common'
|
10
|
+
require 'flare/util/conversion'
|
79
11
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return date + "h"
|
85
|
-
end
|
12
|
+
#
|
13
|
+
module Flare
|
14
|
+
module Tools
|
15
|
+
module Cli
|
86
16
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
17
|
+
# == Description
|
18
|
+
#
|
19
|
+
class Stats < SubCommand
|
20
|
+
include Flare::Util::Conversion
|
21
|
+
include Flare::Util::Logging
|
22
|
+
include Flare::Tools::Common
|
23
|
+
|
24
|
+
myname :stats
|
25
|
+
desc "show the statistics of a flare cluster."
|
26
|
+
usage "stats [hostname:port] ..."
|
27
|
+
|
28
|
+
HeaderConfig = [ ['%-25.25s', 'hostname:port'],
|
29
|
+
['%7s', 'state'],
|
30
|
+
['%6s', 'role'],
|
31
|
+
['%9s', 'partition'],
|
32
|
+
['%7s', 'balance'],
|
33
|
+
['%8.8s', 'items'],
|
34
|
+
['%4s', 'conn'],
|
35
|
+
['%6.6s', 'behind'],
|
36
|
+
['%3.3s', 'hit'],
|
37
|
+
['%4.4s', 'size'],
|
38
|
+
['%6.6s', 'uptime'],
|
39
|
+
['%7s', 'version'] ]
|
40
|
+
|
41
|
+
def setup(opt)
|
42
|
+
opt.on("-q", '--qps', "show qps") {|v| @qps = v}
|
43
|
+
opt.on("-w", '--wait=SECOND', "specify wait time for repeat(second)") {|v| @wait = v.to_i}
|
44
|
+
opt.on("-c", '--count=REPEATTIME', "specify repeat count") {|v| @count = v.to_i}
|
45
|
+
opt.on("-d", '--delimiter=CHAR', "spedify delimiter") {|v| @delimiter = v}
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
super
|
50
|
+
@qps = false
|
51
|
+
@wait = 1
|
52
|
+
@count = 1
|
53
|
+
@cont = true
|
54
|
+
@delimiter = ' '
|
55
|
+
end
|
56
|
+
|
57
|
+
def interrupt
|
58
|
+
puts "INTERRUPTED"
|
59
|
+
@cont = false
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute(config, *args)
|
63
|
+
nodes = {}
|
64
|
+
threads = {}
|
65
|
+
header = Marshal.load(Marshal.dump(HeaderConfig))
|
66
|
+
header << ['%5.5s', 'qps'] << ['%5.5s', 'qps-r'] << ['%5.5s', 'qps-w'] if @qps
|
67
|
+
|
68
|
+
Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout]) do |s|
|
69
|
+
nodes = s.stats_nodes
|
70
|
+
unless nodes
|
71
|
+
error "Invalid index server."
|
72
|
+
return S_NG
|
73
|
+
end
|
74
|
+
nodes = nodes.sort_by{|key,val| [val['partition'].to_i, val['role'], key]}
|
75
|
+
threads = s.stats_threads_by_peer
|
76
|
+
end
|
77
|
+
|
78
|
+
worker_threads = []
|
79
|
+
queue = {}
|
80
|
+
|
81
|
+
nodes.each do |hostname_port,data|
|
82
|
+
hostname, port = hostname_port.split(":", 2)
|
83
|
+
queue[hostname_port] = SizedQueue.new(1)
|
84
|
+
worker_threads << Thread.new(queue[hostname_port]) do |q|
|
85
|
+
s = nil
|
86
|
+
while @cont
|
87
|
+
stats_data = nil
|
88
|
+
begin
|
89
|
+
s = Flare::Tools::Stats.open(hostname, data['port'], config[:timeout])
|
90
|
+
stats = s.stats
|
91
|
+
time = Time.now
|
92
|
+
behind = threads[hostname_port].has_key?('behind') ? threads[hostname_port]['behind'] : "-"
|
93
|
+
uptime_short = short_desc_of_second(stats['uptime'])
|
94
|
+
hit_rate = if stats.has_key?('cmd_get') && stats['cmd_get'] != "0"
|
95
|
+
cmd_get = stats['cmd_get'].to_f
|
96
|
+
get_hits = stats['get_hits'].to_f
|
97
|
+
(get_hits / cmd_get * 100.0).round
|
98
|
+
else
|
99
|
+
"-"
|
100
|
+
end
|
101
|
+
size = stats['bytes'] == "0" ? "-" : (stats['bytes'].to_i / 1024 / 1024 / 1024) # gigabyte
|
102
|
+
stats_data = {
|
103
|
+
:hostname => hostname,
|
104
|
+
:port => port,
|
105
|
+
:hostname_port => "#{hostname}:#{port}",
|
106
|
+
:state => data['state'],
|
107
|
+
:role => data['role'],
|
108
|
+
:partition => data['partition'] == "-1" ? "-" : data['partition'],
|
109
|
+
:balance => data['balance'],
|
110
|
+
:items => stats['curr_items'],
|
111
|
+
:conn => stats['curr_connections'],
|
112
|
+
:behind => behind,
|
113
|
+
:hit_rate => hit_rate,
|
114
|
+
:size => size,
|
115
|
+
:uptime => stats['uptime'],
|
116
|
+
:uptime_short => uptime_short,
|
117
|
+
:version => stats['version'],
|
118
|
+
:cmd_get => stats['cmd_get'],
|
119
|
+
:cmd_set => stats['cmd_set'],
|
120
|
+
:time => time,
|
121
|
+
}
|
122
|
+
rescue Errno::ECONNREFUSED => e
|
123
|
+
rescue => e
|
124
|
+
begin
|
125
|
+
s.close unless s.nil?
|
126
|
+
rescue => close_error
|
127
|
+
end
|
128
|
+
s = nil
|
129
|
+
end
|
130
|
+
if stats_data.nil?
|
131
|
+
stats_data = {
|
132
|
+
:hostname_port => "#{hostname}:#{port}",
|
133
|
+
}
|
134
|
+
end
|
135
|
+
q.push stats_data
|
136
|
+
end
|
137
|
+
s.close unless s.nil?
|
138
|
+
end # Thread.new
|
139
|
+
end # nodes.each
|
140
|
+
|
141
|
+
query_prev = {} if @qps
|
142
|
+
|
143
|
+
if @count > 1 || @qps
|
144
|
+
interruptible {sleep 1}
|
145
|
+
end
|
146
|
+
|
147
|
+
s = Flare::Tools::IndexServer.open(config[:index_server_hostname], config[:index_server_port], config[:timeout])
|
148
|
+
unless s
|
149
|
+
error "Couldn't connect to the index server."
|
150
|
+
return S_NG
|
151
|
+
end
|
152
|
+
|
153
|
+
(0...@count).each do |i|
|
154
|
+
nodes = s.stats_nodes
|
155
|
+
unless nodes
|
156
|
+
error "Invalid index server."
|
157
|
+
exit 1
|
158
|
+
end
|
159
|
+
nodes = nodes.sort_by{|key,val| [val['partition'].to_i, val['role'], key]}
|
160
|
+
threads = s.stats_threads_by_peer
|
161
|
+
|
162
|
+
break unless @cont
|
163
|
+
max_nodekey_length = 25
|
164
|
+
nodes.each do |k, n|
|
165
|
+
max_nodekey_length = k.length if k.length > max_nodekey_length
|
166
|
+
end
|
167
|
+
header[0][0] = "%-#{max_nodekey_length}.#{max_nodekey_length}s"
|
168
|
+
format = header.map {|x| x[0]}.join(@delimiter)
|
169
|
+
label = format % header.map{|x| x[1]}.flatten
|
170
|
+
puts label
|
171
|
+
nodes.each do |k, n|
|
172
|
+
stats_data = queue[k].pop
|
173
|
+
next if (args.size > 0 && !args.include?(k))
|
174
|
+
stats_data[:state] = n['state']
|
175
|
+
stats_data[:role] = n['role']
|
176
|
+
stats_data[:partition] = n['partition']
|
177
|
+
stats_data[:balance] = n['balance']
|
178
|
+
stats_data[:behind] = (threads.has_key?(k) || threads[k].has_key?('behind')) ? threads[k]['behind'] : "-"
|
179
|
+
output = [:hostname_port, :state, :role, :partition, :balance, :items,
|
180
|
+
:conn, :behind, :hit_rate, :size, :uptime_short, :version].map {|x| stats_data[x]}
|
181
|
+
if @qps
|
182
|
+
query = {}
|
183
|
+
query[:query] = stats_data[:cmd_get].to_i+stats_data[:cmd_set].to_i
|
184
|
+
query[:query_r] = stats_data[:cmd_get].to_i
|
185
|
+
query[:query_w] = stats_data[:cmd_set].to_i
|
186
|
+
query[:time] = time = stats_data[:time]
|
187
|
+
if query_prev.has_key?(k)
|
188
|
+
duration = (time-query_prev[k][:time]).to_f
|
189
|
+
[:query, :query_r, :query_w].each do |x|
|
190
|
+
diff = (query[x]-query_prev[k][x]).to_f
|
191
|
+
qps = if diff > 0 then diff/duration else 0 end
|
192
|
+
output << qps
|
193
|
+
end
|
194
|
+
else
|
195
|
+
output << 0 << 0 << 0
|
196
|
+
end
|
197
|
+
query_prev[k] = query.dup
|
198
|
+
end
|
199
|
+
|
200
|
+
puts format % output
|
201
|
+
end
|
202
|
+
interruptible {
|
203
|
+
wait_for_stats
|
204
|
+
}
|
205
|
+
end
|
206
|
+
s.close
|
207
|
+
|
208
|
+
@cont = false
|
209
|
+
|
210
|
+
queue.each do |k,q|
|
211
|
+
q.clear
|
212
|
+
end
|
213
|
+
|
214
|
+
interruptible {
|
215
|
+
worker_threads.each do |t|
|
216
|
+
t.join
|
217
|
+
end
|
218
|
+
}
|
219
|
+
|
220
|
+
S_OK
|
221
|
+
end
|
222
|
+
|
223
|
+
def wait_for_stats
|
224
|
+
if @qps || @count > 1
|
225
|
+
wait = @wait
|
226
|
+
while wait > 0 && @cont
|
227
|
+
sleep 1
|
228
|
+
wait -= 1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
103
233
|
end
|
104
|
-
stats = self.get_stats(ipaddr, data['port'])
|
105
|
-
partition = data['partition'] == "-1" ? "-" : data['partition']
|
106
|
-
behind = threads[hostname_port].key?('behind') ? threads[hostname_port]['behind'] : "-"
|
107
|
-
uptime = self.str_date(stats['uptime'])
|
108
|
-
hit_rate = stats['cmd_get'] == "0" ? "-" : (stats['get_hits'].to_f / stats['cmd_get'].to_f * 100.0).round
|
109
|
-
size = stats['bytes'] == "0" ? "-" : (stats['bytes'].to_i / 1024 / 1024 / 1024)
|
110
|
-
str += @format % [
|
111
|
-
hostname,
|
112
|
-
port,
|
113
|
-
data['state'],
|
114
|
-
data['role'],
|
115
|
-
partition,
|
116
|
-
data['balance'],
|
117
|
-
stats['curr_items'],
|
118
|
-
stats['curr_connections'],
|
119
|
-
behind,
|
120
|
-
hit_rate,
|
121
|
-
size,
|
122
|
-
uptime,
|
123
|
-
stats["version"],
|
124
|
-
]
|
125
234
|
end
|
126
|
-
puts @label + str
|
127
235
|
end
|
128
|
-
# }}}
|
129
|
-
end
|
130
236
|
end
|
237
|
+
|