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.
Files changed (100) hide show
  1. data/.gemtest +0 -0
  2. data/Flare-tools.txt +0 -0
  3. data/History.txt +114 -2
  4. data/LICENSE +21 -0
  5. data/Manifest.txt +65 -8
  6. data/README.txt +356 -0
  7. data/Rakefile +90 -25
  8. data/Tutorial.txt +370 -0
  9. data/bin/flare-admin +6 -0
  10. data/bin/flare-argv0 +6 -0
  11. data/bin/flare-deploy +6 -0
  12. data/bin/flare-keychecker +6 -0
  13. data/bin/flare-part +6 -0
  14. data/bin/flare-ping +6 -0
  15. data/bin/flare-stats +4 -10
  16. data/bin/flare-zkadmin +6 -0
  17. data/lib/flare/net/connection.rb +98 -0
  18. data/lib/flare/test/cluster.rb +140 -0
  19. data/lib/flare/test/daemon.rb +144 -0
  20. data/lib/flare/test/node.rb +62 -0
  21. data/lib/flare/tools.rb +18 -16
  22. data/lib/flare/tools/cli.rb +32 -0
  23. data/lib/flare/tools/cli/activate.rb +106 -0
  24. data/lib/flare/tools/cli/balance.rb +83 -0
  25. data/lib/flare/tools/cli/cli_util.rb +77 -0
  26. data/lib/flare/tools/cli/deploy.rb +170 -0
  27. data/lib/flare/tools/cli/down.rb +85 -0
  28. data/lib/flare/tools/cli/dump.rb +219 -0
  29. data/lib/flare/tools/cli/dumpkey.rb +117 -0
  30. data/lib/flare/tools/cli/flare_admin.rb +81 -0
  31. data/lib/flare/tools/cli/flare_argv0.rb +60 -0
  32. data/lib/flare/tools/cli/flare_keychecker.rb +106 -0
  33. data/lib/flare/tools/cli/flare_zkadmin.rb +226 -0
  34. data/lib/flare/tools/cli/index.rb +54 -0
  35. data/lib/flare/tools/cli/list.rb +93 -0
  36. data/lib/flare/tools/cli/master.rb +143 -0
  37. data/lib/flare/tools/cli/part.rb +100 -0
  38. data/lib/flare/tools/cli/ping.rb +81 -0
  39. data/lib/flare/tools/cli/reconstruct.rb +164 -0
  40. data/lib/flare/tools/cli/remove.rb +119 -0
  41. data/lib/flare/tools/cli/restore.rb +180 -0
  42. data/lib/flare/tools/cli/slave.rb +125 -0
  43. data/lib/flare/tools/cli/stats.rb +229 -122
  44. data/lib/flare/tools/cli/sub_command.rb +73 -0
  45. data/lib/flare/tools/cli/summary.rb +97 -0
  46. data/lib/flare/tools/cli/threads.rb +78 -0
  47. data/lib/flare/tools/cli/verify.rb +202 -0
  48. data/lib/flare/tools/client.rb +267 -0
  49. data/lib/flare/tools/cluster.rb +319 -0
  50. data/lib/flare/tools/common.rb +196 -0
  51. data/lib/flare/tools/index_server.rb +51 -0
  52. data/lib/flare/tools/node.rb +162 -0
  53. data/lib/flare/tools/stats.rb +75 -0
  54. data/lib/flare/tools/zk_util.rb +28 -0
  55. data/lib/flare/util.rb +34 -0
  56. data/lib/flare/util/bwlimit.rb +132 -0
  57. data/lib/flare/util/command_line.rb +79 -0
  58. data/lib/flare/util/conf.rb +71 -0
  59. data/lib/flare/util/constant.rb +25 -0
  60. data/lib/flare/util/conversion.rb +26 -0
  61. data/lib/flare/util/default_logger.rb +52 -0
  62. data/lib/flare/util/exception.rb +19 -0
  63. data/lib/flare/util/filesystem.rb +30 -0
  64. data/lib/flare/util/flared_conf.rb +33 -0
  65. data/lib/flare/util/flarei_conf.rb +32 -0
  66. data/lib/flare/util/hash_function.rb +32 -0
  67. data/lib/flare/util/interruption.rb +70 -0
  68. data/lib/flare/util/key_resolver.rb +67 -0
  69. data/lib/flare/util/log4r_logger.rb +79 -0
  70. data/lib/flare/util/logger.rb +40 -0
  71. data/lib/flare/util/logging.rb +84 -0
  72. data/lib/flare/util/result.rb +53 -0
  73. data/test/test/experimental/cache_test.rb +113 -0
  74. data/test/test/experimental/key_distribution_test.rb +38 -0
  75. data/test/test/experimental/keychecker_test.rb +60 -0
  76. data/test/test/experimental/list_test.rb +108 -0
  77. data/test/test/extra/replication_test.rb +184 -0
  78. data/test/test/integration/cli_test.rb +348 -0
  79. data/test/test/integration/dump_expired_test.rb +103 -0
  80. data/test/test/integration/dump_test.rb +128 -0
  81. data/test/test/integration/index_server_test.rb +35 -0
  82. data/test/test/integration/node_test.rb +78 -0
  83. data/test/test/integration/partition_test.rb +235 -0
  84. data/test/test/integration/proxy_test.rb +54 -0
  85. data/test/test/integration/stats_test.rb +79 -0
  86. data/test/test/system/flare_admin_test.rb +191 -0
  87. data/test/test/unit/bwlimit_test.rb +52 -0
  88. data/test/test/unit/cluster_test.rb +96 -0
  89. data/test/test/unit/daemon_test.rb +30 -0
  90. data/test/test/unit/logger_test.rb +46 -0
  91. data/test/test/unit/tools_test.rb +25 -0
  92. data/test/test/unit/util_test.rb +70 -0
  93. metadata +239 -84
  94. data/README.rdoc +0 -83
  95. data/bin/flare-partition-setting +0 -12
  96. data/lib/flare/tools/cli/partition_setting.rb +0 -86
  97. data/lib/flare/tools/core.rb +0 -189
  98. data/lib/flare/tools/logger.rb +0 -31
  99. data/test/test_flare-tools.rb +0 -11
  100. 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
- # Author:: kgws (http://d.hatena.ne.jp/kgws/)
3
- # Copyright:: Copyright (c) 2010- kgws.
4
- # License:: This program is licenced under the same licence as kgws.
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
- # min
74
- if date >= 60
75
- date = date / 60
76
- else
77
- return date + "m"
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
- # hour
81
- if date >= 24
82
- date = date / 24
83
- else
84
- return date + "h"
85
- end
12
+ #
13
+ module Flare
14
+ module Tools
15
+ module Cli
86
16
 
87
- # day
88
- "#{date}d"
89
- end
90
- # }}}
91
- # {{{ execute
92
- def execute
93
- str = ""
94
- nodes = self.get_stats_nodes
95
- nodes = self.sort_node(nodes)
96
- threads = self.get_stats_threads
97
- nodes.each do |hostname_port,data|
98
- ipaddr, port = hostname_port.split(":", 2)
99
- begin
100
- hostname = Resolv.getname(ipaddr).to_s
101
- rescue Resolv::ResolvError
102
- hostname = ipaddr
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
+