errdb 3.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/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
+