kjess 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CONTRIBUTING.md +45 -0
  2. data/HISTORY.rdoc +5 -0
  3. data/LICENSE +16 -0
  4. data/Manifest.txt +53 -0
  5. data/README.rdoc +44 -0
  6. data/Rakefile +337 -0
  7. data/example/client_test.rb +73 -0
  8. data/lib/kjess.rb +11 -0
  9. data/lib/kjess/client.rb +289 -0
  10. data/lib/kjess/connection.rb +119 -0
  11. data/lib/kjess/error.rb +5 -0
  12. data/lib/kjess/protocol.rb +76 -0
  13. data/lib/kjess/request.rb +31 -0
  14. data/lib/kjess/request/delete.rb +11 -0
  15. data/lib/kjess/request/dump_stats.rb +7 -0
  16. data/lib/kjess/request/flush.rb +11 -0
  17. data/lib/kjess/request/flush_all.rb +7 -0
  18. data/lib/kjess/request/get.rb +21 -0
  19. data/lib/kjess/request/quit.rb +7 -0
  20. data/lib/kjess/request/reload.rb +7 -0
  21. data/lib/kjess/request/set.rb +19 -0
  22. data/lib/kjess/request/shutdown.rb +7 -0
  23. data/lib/kjess/request/stats.rb +7 -0
  24. data/lib/kjess/request/status.rb +8 -0
  25. data/lib/kjess/request/version.rb +8 -0
  26. data/lib/kjess/response.rb +76 -0
  27. data/lib/kjess/response/client_error.rb +19 -0
  28. data/lib/kjess/response/deleted.rb +5 -0
  29. data/lib/kjess/response/dumped_stats.rb +48 -0
  30. data/lib/kjess/response/end.rb +5 -0
  31. data/lib/kjess/response/eof.rb +5 -0
  32. data/lib/kjess/response/error.rb +13 -0
  33. data/lib/kjess/response/flushed_all_queues.rb +6 -0
  34. data/lib/kjess/response/not_found.rb +5 -0
  35. data/lib/kjess/response/not_stored.rb +5 -0
  36. data/lib/kjess/response/reloaded_config.rb +6 -0
  37. data/lib/kjess/response/server_error.rb +18 -0
  38. data/lib/kjess/response/stats.rb +60 -0
  39. data/lib/kjess/response/stored.rb +5 -0
  40. data/lib/kjess/response/unknown.rb +3 -0
  41. data/lib/kjess/response/value.rb +26 -0
  42. data/lib/kjess/response/version.rb +10 -0
  43. data/lib/kjess/stats_cache.rb +31 -0
  44. data/spec/client_spec.rb +265 -0
  45. data/spec/kestrel_server.rb +137 -0
  46. data/spec/request/set_spec.rb +13 -0
  47. data/spec/request/version_spec.rb +17 -0
  48. data/spec/request_spec.rb +30 -0
  49. data/spec/response/client_error_spec.rb +17 -0
  50. data/spec/spec_helper.rb +12 -0
  51. data/spec/utils.rb +18 -0
  52. data/spec/version_spec.rb +9 -0
  53. data/tasks/kestrel.rake +70 -0
  54. 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
+
@@ -0,0 +1,11 @@
1
+ module KJess
2
+ VERSION = "1.0.0"
3
+ end
4
+
5
+ require 'kjess/connection'
6
+ require 'kjess/stats_cache'
7
+ require 'kjess/client'
8
+ require 'kjess/error'
9
+ require 'kjess/protocol'
10
+ require 'kjess/request'
11
+ require 'kjess/response'
@@ -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