kjess 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING.md +45 -0
- data/HISTORY.rdoc +5 -0
- data/LICENSE +16 -0
- data/Manifest.txt +53 -0
- data/README.rdoc +44 -0
- data/Rakefile +337 -0
- data/example/client_test.rb +73 -0
- data/lib/kjess.rb +11 -0
- data/lib/kjess/client.rb +289 -0
- data/lib/kjess/connection.rb +119 -0
- data/lib/kjess/error.rb +5 -0
- data/lib/kjess/protocol.rb +76 -0
- data/lib/kjess/request.rb +31 -0
- data/lib/kjess/request/delete.rb +11 -0
- data/lib/kjess/request/dump_stats.rb +7 -0
- data/lib/kjess/request/flush.rb +11 -0
- data/lib/kjess/request/flush_all.rb +7 -0
- data/lib/kjess/request/get.rb +21 -0
- data/lib/kjess/request/quit.rb +7 -0
- data/lib/kjess/request/reload.rb +7 -0
- data/lib/kjess/request/set.rb +19 -0
- data/lib/kjess/request/shutdown.rb +7 -0
- data/lib/kjess/request/stats.rb +7 -0
- data/lib/kjess/request/status.rb +8 -0
- data/lib/kjess/request/version.rb +8 -0
- data/lib/kjess/response.rb +76 -0
- data/lib/kjess/response/client_error.rb +19 -0
- data/lib/kjess/response/deleted.rb +5 -0
- data/lib/kjess/response/dumped_stats.rb +48 -0
- data/lib/kjess/response/end.rb +5 -0
- data/lib/kjess/response/eof.rb +5 -0
- data/lib/kjess/response/error.rb +13 -0
- data/lib/kjess/response/flushed_all_queues.rb +6 -0
- data/lib/kjess/response/not_found.rb +5 -0
- data/lib/kjess/response/not_stored.rb +5 -0
- data/lib/kjess/response/reloaded_config.rb +6 -0
- data/lib/kjess/response/server_error.rb +18 -0
- data/lib/kjess/response/stats.rb +60 -0
- data/lib/kjess/response/stored.rb +5 -0
- data/lib/kjess/response/unknown.rb +3 -0
- data/lib/kjess/response/value.rb +26 -0
- data/lib/kjess/response/version.rb +10 -0
- data/lib/kjess/stats_cache.rb +31 -0
- data/spec/client_spec.rb +265 -0
- data/spec/kestrel_server.rb +137 -0
- data/spec/request/set_spec.rb +13 -0
- data/spec/request/version_spec.rb +17 -0
- data/spec/request_spec.rb +30 -0
- data/spec/response/client_error_spec.rb +17 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/utils.rb +18 -0
- data/spec/version_spec.rb +9 -0
- data/tasks/kestrel.rake +70 -0
- metadata +193 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#require 'perftools'
|
4
|
+
require 'kjess'
|
5
|
+
require 'trollop'
|
6
|
+
require 'hitimes'
|
7
|
+
|
8
|
+
ACTIONS = %w[ produce consume clear status ].sort
|
9
|
+
|
10
|
+
options = Trollop::options do
|
11
|
+
opt :action, "Which action to perform (#{ACTIONS.join(', ')})", :default => 'produce'
|
12
|
+
opt :qname, "The name of the queue to use" , :default => 'testing'
|
13
|
+
opt :count, "How many messages to produce or consume" , :default => 1000
|
14
|
+
opt :length, "The length of the messages (only for produce)" , :default => 1024
|
15
|
+
opt :host, "The host to contact" , :default => 'localhost'
|
16
|
+
opt :port, "The port number to use" , :default => 22133
|
17
|
+
end
|
18
|
+
|
19
|
+
Trollop::die :action, "must be one of #{ACTIONS.join(', ')}" unless ACTIONS.include?( options[:action] )
|
20
|
+
|
21
|
+
trap( 'INT' ) do
|
22
|
+
puts "Closing down"
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
class ClientTest
|
27
|
+
attr_reader :options
|
28
|
+
attr_reader :client
|
29
|
+
attr_reader :timer
|
30
|
+
attr_reader :item
|
31
|
+
|
32
|
+
def initialize( options )
|
33
|
+
@options = options
|
34
|
+
@client = KJess::Client.new( :host => options[:host], :port => options[:port] )
|
35
|
+
@timer = Hitimes::TimedMetric.new( options[:action] )
|
36
|
+
@item = "x" * options[:length]
|
37
|
+
end
|
38
|
+
|
39
|
+
def time_and_count( &block )
|
40
|
+
options[:count].times do |x|
|
41
|
+
timer.measure do
|
42
|
+
block.call
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return timer.stats.to_hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def produce
|
49
|
+
puts "Inserting #{options[:count]} items into the queue at #{options[:host]}:#{options[:port]}"
|
50
|
+
time_and_count do
|
51
|
+
client.set( options[:qname], item )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def consume
|
56
|
+
puts "Consuming #{options[:count]} items from the queue at #{options[:host]}:#{options[:port]}"
|
57
|
+
time_and_count do
|
58
|
+
client.get( options[:qname] )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_test
|
63
|
+
# PerfTools::CpuProfiler.start( "/tmp/#{options[:action]}_profile" ) do
|
64
|
+
send( options[:action] )
|
65
|
+
# end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
test = ClientTest.new( options )
|
70
|
+
s = test.run_test
|
71
|
+
puts s.inspect
|
72
|
+
|
73
|
+
|
data/lib/kjess.rb
ADDED
data/lib/kjess/client.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'kjess/stats_cache'
|
2
|
+
module KJess
|
3
|
+
class Client
|
4
|
+
# Public: The hostname of the kestrel server to connect to
|
5
|
+
attr_reader :host
|
6
|
+
|
7
|
+
# Public: The port on hostname of the Kestrel server
|
8
|
+
attr_reader :port
|
9
|
+
|
10
|
+
# Public: The admin HTTP Port on the Kestrel server
|
11
|
+
attr_reader :admin_port
|
12
|
+
|
13
|
+
# Internal: The cache of stats
|
14
|
+
attr_reader :stats_cache
|
15
|
+
|
16
|
+
# Public: The default parameters for a client connection to a Kestrel
|
17
|
+
# server.
|
18
|
+
def self.defaults
|
19
|
+
{
|
20
|
+
:host => 'localhost',
|
21
|
+
:port => 22133,
|
22
|
+
:admin_port => 2223,
|
23
|
+
:stats_cache_expiration => 0, # number of seconds to keep stats around
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize( opts = {} )
|
28
|
+
merged = Client.defaults.merge( opts )
|
29
|
+
@host = merged[:host]
|
30
|
+
@port = merged[:port]
|
31
|
+
@admin_port = merged[:admin_port]
|
32
|
+
@stats_cache = StatsCache.new( self, merged[:stats_cache_expiration] )
|
33
|
+
@connection = KJess::Connection.new( host, port )
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Disconnect from the Kestrel server
|
37
|
+
#
|
38
|
+
# Returns nothing
|
39
|
+
def disconnect
|
40
|
+
@connection.close if connected?
|
41
|
+
@connection = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Allocate or return the existing connection to the server
|
45
|
+
#
|
46
|
+
# Returns a KJess::Connection
|
47
|
+
def connection
|
48
|
+
@connection ||= KJess::Connection.new( host, port )
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: is the client connected to a server
|
52
|
+
#
|
53
|
+
# Returns true or false
|
54
|
+
def connected?
|
55
|
+
return false if @connection.nil?
|
56
|
+
return false if @connection.closed?
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Return the version of the Kestrel Server.
|
61
|
+
#
|
62
|
+
# Return a string
|
63
|
+
# Raise Exception if there is a
|
64
|
+
def version
|
65
|
+
v = KJess::Request::Version.new
|
66
|
+
r = send_recv( v )
|
67
|
+
return r.version if Response::Version === r
|
68
|
+
raise KJess::Error, "Unexpected Response from VERSION command"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Add an item to the given queue
|
72
|
+
#
|
73
|
+
# queue_name - the queue to put an item on
|
74
|
+
# item - the item to put on the queue. #to_s will be called on it.
|
75
|
+
# expiration - The number of seconds from now to expire the item
|
76
|
+
#
|
77
|
+
# Returns true if successful, false otherwise
|
78
|
+
def set( queue_name, item, expiration = 0 )
|
79
|
+
s = KJess::Request::Set.new( :queue_name => queue_name, :data => item, :expiration => expiration )
|
80
|
+
send_recv( s )
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Retrieve an item from the given queue
|
84
|
+
#
|
85
|
+
# queue_name - the name of the queue to retrieve an item from
|
86
|
+
# options - the options for retrieving the items
|
87
|
+
# :wait_for - wait for this many ms for an item on the queued(default: 0)
|
88
|
+
# :open - count this as an reliable read (default: false)
|
89
|
+
# :close - close a previous read that was retrieved with :open
|
90
|
+
# :abort - close an existing read, returning that item to the head of the queue
|
91
|
+
# :peek - return the first item on the queue, and do not remove it
|
92
|
+
#
|
93
|
+
# returns a Response
|
94
|
+
def get( queue_name, opts = {} )
|
95
|
+
opts = opts.merge( :queue_name => queue_name )
|
96
|
+
g = KJess::Request::Get.new( opts )
|
97
|
+
resp = send_recv( g )
|
98
|
+
|
99
|
+
return resp.data if KJess::Response::Value === resp
|
100
|
+
return nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Reserve the next item on the queue
|
104
|
+
#
|
105
|
+
# This is a helper method to get an item from a queue and open it for
|
106
|
+
# reliable read.
|
107
|
+
#
|
108
|
+
# queue_name - the name of the queue to retrieve an item from
|
109
|
+
# options - Additional options
|
110
|
+
# :wait_for - wait for this many ms for an item on the queue(default: 0)
|
111
|
+
def reserve( queue_name, opts = {} )
|
112
|
+
opts = opts.merge( :open => true )
|
113
|
+
get( queue_name, opts )
|
114
|
+
end
|
115
|
+
|
116
|
+
# Public: Reserve the next item on the queue and close out the previous
|
117
|
+
# read.
|
118
|
+
#
|
119
|
+
# This is a helper method to do a reliable read on a queue item while
|
120
|
+
# closing out the existing read at the same time.
|
121
|
+
#
|
122
|
+
# queue_name - the name of the quee to retieve and item from
|
123
|
+
# options - Additional options
|
124
|
+
# :wait_for - wait for this many ms for an item on the queue(default: 0)
|
125
|
+
def close_and_reserve( queue_name, opts = {} )
|
126
|
+
opts = opts.merge( :close => true )
|
127
|
+
reserve( queue_name, opts )
|
128
|
+
end
|
129
|
+
|
130
|
+
# Public: Peek at the top item in the queue
|
131
|
+
#
|
132
|
+
# queue_name - the name of the queue to retrieve an item from
|
133
|
+
#
|
134
|
+
# Returns a Response
|
135
|
+
def peek( queue_name )
|
136
|
+
get( queue_name, :peek => true )
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Close an existing reliable read
|
140
|
+
#
|
141
|
+
# queue_name - the name of the queue to abort
|
142
|
+
#
|
143
|
+
# Returns a Response
|
144
|
+
def close( queue_name )
|
145
|
+
get( queue_name, :close => true )
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# Public: Abort an existing reliable read
|
150
|
+
#
|
151
|
+
# queue_name - the name of the queue to abort
|
152
|
+
#
|
153
|
+
# Returns a Response
|
154
|
+
def abort( queue_name )
|
155
|
+
get( queue_name, :abort => true )
|
156
|
+
end
|
157
|
+
|
158
|
+
# Public : Remove a queue from the kestrel server
|
159
|
+
#
|
160
|
+
# This will remove any queue you want. Including queues that do not exist.
|
161
|
+
#
|
162
|
+
# queue_name - the name of the queue to remove
|
163
|
+
#
|
164
|
+
# Returns true if it was deleted false otherwise
|
165
|
+
def delete( queue_name )
|
166
|
+
req = KJess::Request::Delete.new( :queue_name => queue_name )
|
167
|
+
resp = send_recv( req )
|
168
|
+
return KJess::Response::Deleted === resp
|
169
|
+
end
|
170
|
+
|
171
|
+
# Public: Remove all items from a queue on the kestrel server
|
172
|
+
#
|
173
|
+
# This will flush any and all queue. Even queues that do not exist.
|
174
|
+
#
|
175
|
+
# queue_name - the name of the queue to flush
|
176
|
+
#
|
177
|
+
# Returns true if the queue was flushed.
|
178
|
+
def flush( queue_name )
|
179
|
+
req = KJess::Request::Flush.new( :queue_name => queue_name )
|
180
|
+
resp = send_recv( req )
|
181
|
+
return KJess::Response::End === resp
|
182
|
+
end
|
183
|
+
|
184
|
+
# Public: Remove all items from all queues on the kestrel server
|
185
|
+
#
|
186
|
+
# Returns true.
|
187
|
+
def flush_all
|
188
|
+
resp = send_recv( KJess::Request::FlushAll.new )
|
189
|
+
return KJess::Response::End === resp
|
190
|
+
end
|
191
|
+
|
192
|
+
# Public: Have Kestrel reload its config.
|
193
|
+
#
|
194
|
+
# Currently the kestrel server will say that the config was reloaded no
|
195
|
+
# matter what so there is no way to determine if the config failed to load.
|
196
|
+
#
|
197
|
+
# Returns true
|
198
|
+
def reload
|
199
|
+
resp = send_recv( KJess::Request::Reload.new )
|
200
|
+
return KJess::Response::ReloadedConfig === resp
|
201
|
+
end
|
202
|
+
|
203
|
+
# Public: Disconnect from the kestrel server.
|
204
|
+
#
|
205
|
+
# Returns true
|
206
|
+
def quit
|
207
|
+
resp = send_recv( KJess::Request::Quit.new )
|
208
|
+
return KJess::Response::Eof === resp
|
209
|
+
end
|
210
|
+
|
211
|
+
# Public: Return the server status.
|
212
|
+
#
|
213
|
+
# Currently this is only supported in the HEAD versin of kestrel. Version
|
214
|
+
# where this is not available will raise ServerError.
|
215
|
+
#
|
216
|
+
# Returns a String.
|
217
|
+
def status( update_to = nil )
|
218
|
+
resp = send_recv( KJess::Request::Status.new( update_to ) )
|
219
|
+
raise KJess::Error, "Status command is not supported" if KJess::Response::ClientError === resp
|
220
|
+
return resp.message
|
221
|
+
end
|
222
|
+
|
223
|
+
# Public: Return stats about the Kestrel server, they will be cached
|
224
|
+
# according to the stats_cache_expiration initialization parameter
|
225
|
+
#
|
226
|
+
# Returns a Hash
|
227
|
+
def stats
|
228
|
+
stats_cache.stats
|
229
|
+
end
|
230
|
+
|
231
|
+
# Internal: Return the hash of stats
|
232
|
+
#
|
233
|
+
# Using a combination of the STATS and DUMP_STATS commands this generates a
|
234
|
+
# good overview of all the most used stats for a Kestrel server.
|
235
|
+
#
|
236
|
+
# Returns a Hash
|
237
|
+
def stats!
|
238
|
+
stats = send_recv( KJess::Request::Stats.new )
|
239
|
+
h = stats.data
|
240
|
+
dump_stats = send_recv( KJess::Request::DumpStats.new )
|
241
|
+
h['queues'] = Hash.new
|
242
|
+
if KJess::Response::DumpedStats === dump_stats then
|
243
|
+
h['queues'].merge!( dump_stats.data )
|
244
|
+
end
|
245
|
+
return h
|
246
|
+
end
|
247
|
+
|
248
|
+
# Public: Returns true if the server is alive
|
249
|
+
#
|
250
|
+
# This uses the 'stats' method to see if the server is alive
|
251
|
+
#
|
252
|
+
# Returns true or false
|
253
|
+
def ping
|
254
|
+
stats
|
255
|
+
true
|
256
|
+
rescue Errno::ECONNREFUSED => e
|
257
|
+
puts e
|
258
|
+
false
|
259
|
+
end
|
260
|
+
|
261
|
+
# Public: Return just the stats about a particular queue
|
262
|
+
#
|
263
|
+
# Returns a Hash
|
264
|
+
def queue_stats( queue_name )
|
265
|
+
stats['queues'][queue_name]
|
266
|
+
end
|
267
|
+
|
268
|
+
# Public: Tells the Kestrel server to shutdown
|
269
|
+
#
|
270
|
+
# Returns nothing
|
271
|
+
def shutdown
|
272
|
+
send_recv( KJess::Request::Shutdown.new )
|
273
|
+
end
|
274
|
+
|
275
|
+
# Internal: Send and recive a request/response
|
276
|
+
#
|
277
|
+
# request - the Request objec to send to the server
|
278
|
+
#
|
279
|
+
# Returns a Response object
|
280
|
+
def send_recv( request )
|
281
|
+
connection.write( request.to_protocol )
|
282
|
+
line = connection.readline
|
283
|
+
resp = KJess::Response.parse( line )
|
284
|
+
resp.read_more( connection )
|
285
|
+
raise resp if resp.error?
|
286
|
+
return resp
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'fcntl'
|
2
|
+
require 'socket'
|
3
|
+
require 'resolv'
|
4
|
+
require 'resolv-replace'
|
5
|
+
require 'kjess/error'
|
6
|
+
|
7
|
+
module KJess
|
8
|
+
# Connection
|
9
|
+
class Connection
|
10
|
+
class Error < KJess::Error; end
|
11
|
+
|
12
|
+
CRLF = "\r\n"
|
13
|
+
|
14
|
+
# Public:
|
15
|
+
# The hostname/ip address to connect to
|
16
|
+
attr_reader :host
|
17
|
+
|
18
|
+
# Public
|
19
|
+
# The port number to connect to. Default 22133
|
20
|
+
attr_reader :port
|
21
|
+
|
22
|
+
def initialize( host, port = 22133 )
|
23
|
+
@host = host
|
24
|
+
@port = Float( port ).to_i
|
25
|
+
@socket = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: Return the raw socket that is connected to the Kestrel server
|
29
|
+
#
|
30
|
+
# Returns the raw socket. If the socket is not connected it will connect and
|
31
|
+
# then return it.
|
32
|
+
#
|
33
|
+
# Returns a TCPSocket
|
34
|
+
def socket
|
35
|
+
return @socket if @socket and not @socket.closed?
|
36
|
+
return @socket = connect()
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal: Create the socket we use to talk to the Kestrel server
|
40
|
+
#
|
41
|
+
# Returns a TCPSocket
|
42
|
+
def connect
|
43
|
+
sock = TCPSocket.new( host, port )
|
44
|
+
|
45
|
+
# close file descriptors if we exec or something like that
|
46
|
+
sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
47
|
+
|
48
|
+
# Disable Nagle's algorithm
|
49
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
50
|
+
|
51
|
+
# limit only to IPv4?
|
52
|
+
# addr = ::Socket.getaddrinfo(host, nil, Socket::AF_INET)
|
53
|
+
# sock = ::Socket.new(::Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
54
|
+
# saddr = ::Socket.pack_sockaddr_in(port, addr[0][3])
|
55
|
+
|
56
|
+
# tcp keepalive
|
57
|
+
# :SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
|
58
|
+
# @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
59
|
+
# @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
|
60
|
+
# @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
|
61
|
+
# @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
|
62
|
+
return sock
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: close the socket if it is not already closed
|
66
|
+
#
|
67
|
+
# Returns nothing
|
68
|
+
def close
|
69
|
+
@socket.close if @socket and not @socket.closed?
|
70
|
+
@socket = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: is the socket closed
|
74
|
+
#
|
75
|
+
# Returns true or false
|
76
|
+
def closed?
|
77
|
+
return true if @socket.nil?
|
78
|
+
return true if @socket.closed?
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Internal: write the given item to the socket
|
83
|
+
#
|
84
|
+
# msg - the message to write
|
85
|
+
#
|
86
|
+
# Returns nothing
|
87
|
+
def write( msg )
|
88
|
+
$stderr.write "--> #{msg}" if $DEBUG
|
89
|
+
socket.write( msg )
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal: read a single line from the socket
|
93
|
+
#
|
94
|
+
# eom - the End Of Mesasge delimiter (default: "\r\n")
|
95
|
+
#
|
96
|
+
# Returns a String
|
97
|
+
def readline( eom = Protocol::CRLF )
|
98
|
+
while line = socket.readline( eom ) do
|
99
|
+
$stderr.write "<-- #{line}" if $DEBUG
|
100
|
+
break unless line.strip.length == 0
|
101
|
+
end
|
102
|
+
return line
|
103
|
+
rescue EOFError
|
104
|
+
close
|
105
|
+
return "EOF"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Read from the socket
|
109
|
+
#
|
110
|
+
# args - this method takes the same arguments as IO#read
|
111
|
+
#
|
112
|
+
# Returns what IO#read returns
|
113
|
+
def read( *args )
|
114
|
+
d = socket.read( *args )
|
115
|
+
$stderr.puts "<-- #{d}" if $DEBUG
|
116
|
+
return d
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|