errdb 3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # 0.1.0 (2011-08-15)
2
+
3
+ * first release
4
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ery Lee <ery.lee@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # errdb-rb
2
+
3
+ A Ruby client library for the [Errdb](http://github.com/erylee/errdb).
4
+
5
+ ## Getting started
6
+
7
+ You can connect to Errdb by instantiating the `Redis` class:
8
+
9
+ require "errdb"
10
+
11
+ errdb = Errdb.new({:host => "localhost", :port => 7272})
12
+
13
+ Once connected, you can start running commands against Errdb:
14
+
15
+ errdb.insert "key", 9999, {"metric1" => 10, "metric2" => 20}
16
+
17
+ errdb.fetch "key", "metric1,metric2", 0, 999999999
18
+
19
+
data/TODO.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.1
2
+
3
+ * Fix 0.1.0 bugs
data/lib/errdb.rb ADDED
@@ -0,0 +1,397 @@
1
+ # encoding: utf-8
2
+
3
+ require 'socket'
4
+
5
+ require 'net/protocol'
6
+
7
+ require 'errdb/version.rb'
8
+
9
+ begin
10
+ # Try to use the SystemTimer gem instead of Ruby's timeout library
11
+ # when running on Ruby 1.8.x. See:
12
+ # http://ph7spot.com/articles/system_timer
13
+ # We don't want to bother trying to load SystemTimer on jruby,
14
+ # ruby 1.9+ and rbx.
15
+ if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == 'ruby' && RUBY_VERSION < '1.9.0')
16
+ require 'system_timer'
17
+ ErrdbTimer = SystemTimer
18
+ else
19
+ require 'timeout'
20
+ ErrdbTimer = Timeout
21
+ end
22
+ rescue LoadError => e
23
+ require 'timeout'
24
+ ErrdbTimer = Timeout
25
+ end
26
+
27
+ ##
28
+ # A Ruby client library for errdb.
29
+ #
30
+
31
+ class Errdb
32
+
33
+ ##
34
+ # Default options for the cache object.
35
+
36
+ DEFAULT_OPTIONS = {
37
+ :readonly => true,
38
+ :timeout => 5,
39
+ :logger => nil,
40
+ :no_reply => false,
41
+ :check_size => true,
42
+ }
43
+
44
+ ##
45
+ # Default errdb port.
46
+
47
+ DEFAULT_PORT = 7272
48
+
49
+ ##
50
+ # The server this client talks to. Play at your own peril.
51
+
52
+ attr_reader :host
53
+
54
+ attr_reader :port
55
+
56
+ attr_reader :server
57
+
58
+ ##
59
+ # Socket timeout limit with this client, defaults to 0.5 sec.
60
+ # Set to nil to disable timeouts.
61
+
62
+ attr_reader :timeout
63
+
64
+ ##
65
+
66
+ ##
67
+ # Log debug/info/warn/error to the given Logger, defaults to nil.
68
+
69
+ attr_reader :logger
70
+
71
+ ##
72
+ # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
73
+ # omitted. See +servers=+ for acceptable server list arguments.
74
+ #
75
+ # Valid options for +opts+ are:
76
+ #
77
+ # [:namespace] Prepends this value to all keys added or retrieved.
78
+ # [:readonly] Raises an exception on cache writes when true.
79
+ # [:timeout] Time to use as the socket read timeout. Defaults to 0.5 sec,
80
+ # set to nil to disable timeouts.
81
+ # [:logger] Logger to use for info/debug output, defaults to nil
82
+ # [:no_reply] Don't bother looking for a reply for write operations (i.e. they
83
+ # become 'fire and forget'), errdb 1.2.5 and later only, speeds up
84
+ # set/add/delete/incr/decr significantly.
85
+ # [:check_size] Raises a ErrdbError if the value to be set is greater than 1 MB, which
86
+ # is the maximum key size for the standard errdb server. Defaults to true.
87
+ # [:autofix_keys] If a key is longer than 250 characters or contains spaces,
88
+ # use an SHA1 hash instead, to prevent collisions on truncated keys.
89
+ # Other options are ignored.
90
+
91
+ def initialize(options = {})
92
+ @host = options[:host] || "127.0.0.1"
93
+ @port = (options[:port] || DEFAULT_PORT).to_i
94
+ @readonly = options[:readonly] || true
95
+ @timeout = (options[:timeout] || 5).to_f
96
+ @logger = options[:logger]
97
+ @server = Server.new(self, @host, @port)
98
+ logger.info { "errdb-client #{VERSION} #{server}" } if logger
99
+ end
100
+
101
+ ##
102
+ # Returns a string representation of the cache object.
103
+
104
+ def inspect
105
+ "<Errdb: %s , ro: %p>" %
106
+ [@server.inspect, @readonly]
107
+ end
108
+
109
+ ##
110
+ # Returns whether there is at least one active server for the object.
111
+
112
+ def active?
113
+ @server.active?
114
+ end
115
+
116
+ ##
117
+ # Returns whether or not the cache object was created read only.
118
+
119
+ def readonly?
120
+ @readonly
121
+ end
122
+
123
+ def insert(key, time, metrics)
124
+ data = (metrics.map do |k, v| "#{k}=#{v}" end).join(",")
125
+ with_socket(server) do |socket|
126
+ socket.write "insert #{key} #{time} #{data}\r\n"
127
+ end
128
+ end
129
+ ##
130
+ # Retrieves +key+ from errdb. If +raw+ is false, the value will be
131
+ # unmarshalled.
132
+
133
+
134
+ #time:a,b,c" =~ /\Atime: (.*)/
135
+
136
+ #"19199191:a,b,c" =~ /\A(\d+):(.*)/
137
+
138
+ def fetch(key, fields, start_time, end_time)
139
+ rows = []
140
+ with_socket(server) do |socket|
141
+ socket.write "fetch #{key} #{fields} #{start_time} #{end_time}\r\n"
142
+ while line = socket.gets do
143
+ raise_on_error_response! line
144
+ break if line == "END\r\n"
145
+ if line =~ /\ATIME:(.*)/ then
146
+ names = $1.strip.split(",")
147
+ elsif line =~ /\A(\d+):(.*)/ then
148
+ timestamp = $1.to_i
149
+ values = $2.strip.split(",").map {|v| v.to_f}
150
+ rows << {timestamp => values}
151
+ else
152
+ logger.warn { "unexpected line: #{line}" } if logger
153
+ end
154
+ end
155
+ end
156
+ rows
157
+ end
158
+
159
+ ##
160
+ # Gets or creates a socket connected to the given server, and yields it
161
+ # to the block
162
+ #
163
+ # If a socket error (SocketError, SystemCallError, IOError) or protocol error
164
+ # (ErrdbError) is raised by the block, closes the socket, attempts to
165
+ # connect again, and retries the block (once). If an error is again raised,
166
+ # reraises it as ErrdbError.
167
+ #
168
+ # If unable to connect to the server (or if in the reconnect wait period),
169
+ # raises ErrdbError. Note that the socket connect code marks a server
170
+ # dead for a timeout period, so retrying does not apply to connection attempt
171
+ # failures (but does still apply to unexpectedly lost connections etc.).
172
+
173
+ def with_socket(server, &block)
174
+
175
+ begin
176
+ socket = server.socket
177
+
178
+ # Raise an IndexError to show this server is out of whack. If were inside
179
+ # a with_server block, we'll catch it and attempt to restart the operation.
180
+
181
+ raise IndexError, "No connection to server (#{server.status})" if socket.nil?
182
+
183
+ block.call(socket)
184
+
185
+ rescue SocketError, Errno::EAGAIN, Timeout::Error => err
186
+ logger.warn { "Socket failure: #{err.message}" } if logger
187
+ server.mark_dead(err)
188
+ handle_error(server, err)
189
+
190
+ rescue ErrdbError, SystemCallError, IOError => err
191
+ logger.warn { "Generic failure: #{err.class.name}: #{err.message}" } if logger
192
+ handle_error(server, err) if socket.nil?
193
+ end
194
+
195
+ end
196
+
197
+ ##
198
+ # Handles +error+ from +server+.
199
+
200
+ def handle_error(server, error)
201
+ raise error if error.is_a?(ErrdbError)
202
+ server.close if server && server.status == "CONNECTED"
203
+ new_error = ErrdbError.new error.message
204
+ new_error.set_backtrace error.backtrace
205
+ raise new_error
206
+ end
207
+
208
+ def raise_on_error_response!(response)
209
+ if response =~ /\A(?:CLIENT_|SERVER_)?ERROR(.*)/
210
+ raise ErrdbError, $1.strip
211
+ end
212
+ end
213
+
214
+ class Server
215
+
216
+ ##
217
+ # The amount of time to wait before attempting to re-establish a
218
+ # connection with a server that is marked dead.
219
+
220
+ RETRY_DELAY = 30.0
221
+
222
+ ##
223
+ # The host the errdb server is running on.
224
+
225
+ attr_reader :host
226
+
227
+ ##
228
+ # The port the errdb server is listening on.
229
+
230
+ attr_reader :port
231
+
232
+ ##
233
+ # The time of next retry if the connection is dead.
234
+
235
+ attr_reader :retry
236
+
237
+ ##
238
+ # A text status string describing the state of the server.
239
+
240
+ attr_reader :status
241
+
242
+ attr_reader :logger
243
+
244
+ ##
245
+ # Create a new Errdb::Server object for the errdb instance
246
+ # listening on the given host and port
247
+
248
+ def initialize(errdb, host, port = DEFAULT_PORT)
249
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
250
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
251
+
252
+ @host = host
253
+ @port = port.to_i
254
+
255
+ @sock = nil
256
+ @retry = nil
257
+ @status = 'NOT CONNECTED'
258
+ @timeout = errdb.timeout
259
+ @logger = errdb.logger
260
+ end
261
+
262
+ ##
263
+ # Return a string representation of the server object.
264
+
265
+ def inspect
266
+ "<Errdb::Server: %s:%d (%s)>" % [@host, @port, @status]
267
+ end
268
+
269
+ ##
270
+ # Check whether the server connection is alive. This will cause the
271
+ # socket to attempt to connect if it isn't already connected and or if
272
+ # the server was previously marked as down and the retry time has
273
+ # been exceeded.
274
+
275
+ def alive?
276
+ !!socket
277
+ end
278
+
279
+ ##
280
+ # Try to connect to the errdb server targeted by this object.
281
+ # Returns the connected socket object on success or nil on failure.
282
+
283
+ def socket
284
+ return @sock if @sock and not @sock.closed?
285
+
286
+ @sock = nil
287
+
288
+ # If the host was dead, don't retry for a while.
289
+ return if @retry and @retry > Time.now
290
+
291
+ # Attempt to connect if not already connected.
292
+ begin
293
+ @sock = connect_to(@host, @port, @timeout)
294
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
295
+ @retry = nil
296
+ @status = 'CONNECTED'
297
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => err
298
+ logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
299
+ mark_dead err
300
+ end
301
+
302
+ return @sock
303
+ end
304
+
305
+ def connect_to(host, port, timeout=nil)
306
+ sock = nil
307
+ if timeout
308
+ ErrdbTimer.timeout(timeout) do
309
+ sock = TCPSocket.new(host, port)
310
+ end
311
+ else
312
+ sock = TCPSocket.new(host, port)
313
+ end
314
+
315
+ io = Errdb::BufferedIO.new(sock)
316
+ io.read_timeout = timeout
317
+ # Getting reports from several customers, including 37signals,
318
+ # that the non-blocking timeouts in 1.7.5 don't seem to be reliable.
319
+ # It can't hurt to set the underlying socket timeout also, if possible.
320
+ if timeout
321
+ secs = Integer(timeout)
322
+ usecs = Integer((timeout - secs) * 1_000_000)
323
+ optval = [secs, usecs].pack("l_2")
324
+ begin
325
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
326
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
327
+ rescue Exception => ex
328
+ # Solaris, for one, does not like/support socket timeouts.
329
+ @logger.info "[errdb-client] Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}" if @logger
330
+ end
331
+ end
332
+ io
333
+ end
334
+
335
+ ##
336
+ # Close the connection to the errdb server targeted by this
337
+ # object. The server is not considered dead.
338
+
339
+ def close
340
+ @sock.close if @sock && !@sock.closed?
341
+ @sock = nil
342
+ @retry = nil
343
+ @status = "NOT CONNECTED"
344
+ end
345
+
346
+ ##
347
+ # Mark the server as dead and close its socket.
348
+
349
+ def mark_dead(error)
350
+ close
351
+ @retry = Time.now + RETRY_DELAY
352
+
353
+ reason = "#{error.class.name}: #{error.message}"
354
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
355
+ @logger.info { @status } if @logger
356
+ end
357
+
358
+ end
359
+
360
+ ##
361
+ # Base Errdb exception class.
362
+
363
+ class ErrdbError < RuntimeError; end
364
+
365
+ class BufferedIO < Net::BufferedIO # :nodoc:
366
+ BUFSIZE = 1024 * 16
367
+
368
+ if RUBY_VERSION < '1.9.1'
369
+ def rbuf_fill
370
+ begin
371
+ @rbuf << @io.read_nonblock(BUFSIZE)
372
+ rescue Errno::EWOULDBLOCK
373
+ retry unless @read_timeout
374
+ if IO.select([@io], nil, nil, @read_timeout)
375
+ retry
376
+ else
377
+ raise Timeout::Error, 'IO timeout'
378
+ end
379
+ end
380
+ end
381
+ end
382
+
383
+ def setsockopt(*args)
384
+ @io.setsockopt(*args)
385
+ end
386
+
387
+ def gets
388
+ encode(readuntil("\n"))
389
+ end
390
+
391
+ def encode(str)
392
+ str
393
+ end
394
+ end
395
+
396
+ end
397
+
@@ -0,0 +1,3 @@
1
+ class Errdb
2
+ VERSION = "3.0"
3
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: errdb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease:
6
+ segments:
7
+ - 3
8
+ - 0
9
+ version: "3.0"
10
+ platform: ruby
11
+ authors:
12
+ - Ery Lee
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-03-30 00:00:00 +08:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Ruby client library for Errdb(Extensibel Round Robin Database)
22
+ email: ery.lee@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - LICENSE
29
+ - README.md
30
+ - TODO.md
31
+ - CHANGELOG.md
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - TODO.md
36
+ - CHANGELOG.md
37
+ - lib/errdb.rb
38
+ - lib/errdb/version.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/erylee/errdb
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.4.2
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Ruby client library for Errdb
73
+ test_files: []
74
+