kjess 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|