namelessjon-couchrest 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/LICENSE +176 -0
- data/README.md +46 -0
- data/Rakefile +69 -0
- data/THANKS.md +21 -0
- data/couchrest.gemspec +111 -0
- data/examples/word_count/markov +38 -0
- data/examples/word_count/views/books/chunked-map.js +3 -0
- data/examples/word_count/views/books/united-map.js +1 -0
- data/examples/word_count/views/markov/chain-map.js +6 -0
- data/examples/word_count/views/markov/chain-reduce.js +7 -0
- data/examples/word_count/views/word_count/count-map.js +6 -0
- data/examples/word_count/views/word_count/count-reduce.js +3 -0
- data/examples/word_count/word_count.rb +46 -0
- data/examples/word_count/word_count_query.rb +40 -0
- data/examples/word_count/word_count_views.rb +26 -0
- data/history.txt +145 -0
- data/lib/couchrest/commands/generate.rb +71 -0
- data/lib/couchrest/commands/push.rb +103 -0
- data/lib/couchrest/database.rb +373 -0
- data/lib/couchrest/design.rb +80 -0
- data/lib/couchrest/document.rb +89 -0
- data/lib/couchrest/helper/attachments.rb +29 -0
- data/lib/couchrest/helper/pager.rb +103 -0
- data/lib/couchrest/helper/streamer.rb +51 -0
- data/lib/couchrest/helper/upgrade.rb +52 -0
- data/lib/couchrest/json_response.rb +14 -0
- data/lib/couchrest/middlewares/logger.rb +263 -0
- data/lib/couchrest/monkeypatches.rb +42 -0
- data/lib/couchrest/response.rb +35 -0
- data/lib/couchrest/rest_api.rb +62 -0
- data/lib/couchrest/server.rb +90 -0
- data/lib/couchrest/support/inheritable_attributes.rb +107 -0
- data/lib/couchrest.rb +127 -0
- data/spec/couchrest/couchrest_spec.rb +202 -0
- data/spec/couchrest/database_spec.rb +870 -0
- data/spec/couchrest/design_spec.rb +158 -0
- data/spec/couchrest/document_spec.rb +279 -0
- data/spec/couchrest/helpers/pager_spec.rb +123 -0
- data/spec/couchrest/helpers/streamer_spec.rb +52 -0
- data/spec/couchrest/server_spec.rb +35 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +44 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +179 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'couchrest/document'
|
2
|
+
module CouchRest
|
3
|
+
class Design < Document
|
4
|
+
def view_by *keys
|
5
|
+
opts = keys.pop if keys.last.is_a?(Hash)
|
6
|
+
opts ||= {}
|
7
|
+
self['views'] ||= {}
|
8
|
+
method_name = "by_#{keys.join('_and_')}"
|
9
|
+
|
10
|
+
if opts[:map]
|
11
|
+
view = {}
|
12
|
+
view['map'] = opts.delete(:map)
|
13
|
+
if opts[:reduce]
|
14
|
+
view['reduce'] = opts.delete(:reduce)
|
15
|
+
opts[:reduce] = false
|
16
|
+
end
|
17
|
+
self['views'][method_name] = view
|
18
|
+
else
|
19
|
+
doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
|
20
|
+
key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
|
21
|
+
guards = opts.delete(:guards) || []
|
22
|
+
guards += doc_keys.map{|k| "(#{k} != null)"}
|
23
|
+
map_function = <<-JAVASCRIPT
|
24
|
+
function(doc) {
|
25
|
+
if (#{guards.join(' && ')}) {
|
26
|
+
emit(#{key_emit}, null);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
JAVASCRIPT
|
30
|
+
self['views'][method_name] = {
|
31
|
+
'map' => map_function
|
32
|
+
}
|
33
|
+
end
|
34
|
+
self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
|
35
|
+
method_name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Dispatches to any named view.
|
39
|
+
# (using the database where this design doc was saved)
|
40
|
+
def view view_name, query={}, &block
|
41
|
+
view_on database, view_name, query, &block
|
42
|
+
end
|
43
|
+
|
44
|
+
# Dispatches to any named view in a specific database
|
45
|
+
def view_on db, view_name, query={}, &block
|
46
|
+
view_name = view_name.to_s
|
47
|
+
view_slug = "#{name}/#{view_name}"
|
48
|
+
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
49
|
+
db.view(view_slug, defaults.merge(query), &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def name
|
53
|
+
id.sub('_design/','') if id
|
54
|
+
end
|
55
|
+
|
56
|
+
def name= newname
|
57
|
+
self['_id'] = "_design/#{newname}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def save
|
61
|
+
raise ArgumentError, "_design docs require a name" unless name && name.length > 0
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# returns stored defaults if the there is a view named this in the design doc
|
68
|
+
def has_view?(view)
|
69
|
+
view = view.to_s
|
70
|
+
self['views'][view] &&
|
71
|
+
(self['views'][view]["couchrest-defaults"] || {})
|
72
|
+
end
|
73
|
+
|
74
|
+
def fetch_view view_name, opts, &block
|
75
|
+
database.view(view_name, opts, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'couchrest/response'
|
2
|
+
require 'couchrest/helper/attachments'
|
3
|
+
module CouchRest
|
4
|
+
class Document < Response
|
5
|
+
include CouchRest::Attachments
|
6
|
+
extend CouchRest::InheritableAttributes
|
7
|
+
|
8
|
+
couchrest_inheritable_accessor :database
|
9
|
+
attr_accessor :database
|
10
|
+
|
11
|
+
# override the CouchRest::Model-wide default_database
|
12
|
+
# This is not a thread safe operation, do not change the model
|
13
|
+
# database at runtime.
|
14
|
+
def self.use_database(db)
|
15
|
+
self.database = db
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
self['_id']
|
20
|
+
end
|
21
|
+
|
22
|
+
def id=(id)
|
23
|
+
self['_id'] = id
|
24
|
+
end
|
25
|
+
|
26
|
+
def rev
|
27
|
+
self['_rev']
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns true if the document has never been saved
|
31
|
+
def new?
|
32
|
+
!rev
|
33
|
+
end
|
34
|
+
alias :new_document? :new?
|
35
|
+
|
36
|
+
# Saves the document to the db using create or update. Also runs the :save
|
37
|
+
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
38
|
+
# CouchDB's response.
|
39
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
|
40
|
+
def save(bulk = false)
|
41
|
+
raise ArgumentError, "doc.database required for saving" unless database
|
42
|
+
result = database.save_doc self, bulk
|
43
|
+
result['ok']
|
44
|
+
end
|
45
|
+
|
46
|
+
# Deletes the document from the database. Runs the :delete callbacks.
|
47
|
+
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
48
|
+
# document to be saved to a new <tt>_id</tt>.
|
49
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
|
50
|
+
# actually be deleted from the db until bulk save.
|
51
|
+
def destroy(bulk = false)
|
52
|
+
raise ArgumentError, "doc.database required to destroy" unless database
|
53
|
+
result = database.delete_doc(self, bulk)
|
54
|
+
if result['ok']
|
55
|
+
self['_rev'] = nil
|
56
|
+
self['_id'] = nil
|
57
|
+
end
|
58
|
+
result['ok']
|
59
|
+
end
|
60
|
+
|
61
|
+
# copies the document to a new id. If the destination id currently exists, a rev must be provided.
|
62
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
63
|
+
# hash with a '_rev' key
|
64
|
+
def copy(dest)
|
65
|
+
raise ArgumentError, "doc.database required to copy" unless database
|
66
|
+
result = database.copy_doc(self, dest)
|
67
|
+
result['ok']
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the CouchDB uri for the document
|
71
|
+
def uri(append_rev = false)
|
72
|
+
return nil if new?
|
73
|
+
couch_uri = "#{database.root}/#{CGI.escape(id)}"
|
74
|
+
if append_rev == true
|
75
|
+
couch_uri << "?rev=#{rev}"
|
76
|
+
elsif append_rev.kind_of?(Integer)
|
77
|
+
couch_uri << "?rev=#{append_rev}"
|
78
|
+
end
|
79
|
+
couch_uri
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the document's database
|
83
|
+
def database
|
84
|
+
@database || self.class.database
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Attachments
|
3
|
+
|
4
|
+
# saves an attachment directly to couchdb
|
5
|
+
def put_attachment(name, file, options={})
|
6
|
+
raise ArgumentError, "doc must be saved" unless self.rev
|
7
|
+
raise ArgumentError, "doc.database required to put_attachment" unless database
|
8
|
+
result = database.put_attachment(self, name, file, options)
|
9
|
+
self['_rev'] = result['rev']
|
10
|
+
result['ok']
|
11
|
+
end
|
12
|
+
|
13
|
+
# returns an attachment's data
|
14
|
+
def fetch_attachment(name)
|
15
|
+
raise ArgumentError, "doc must be saved" unless self.rev
|
16
|
+
raise ArgumentError, "doc.database required to put_attachment" unless database
|
17
|
+
database.fetch_attachment(self, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# deletes an attachment directly from couchdb
|
21
|
+
def delete_attachment(name, force=false)
|
22
|
+
raise ArgumentError, "doc.database required to delete_attachment" unless database
|
23
|
+
result = database.delete_attachment(self, name, force)
|
24
|
+
self['_rev'] = result['rev']
|
25
|
+
result['ok']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Pager
|
3
|
+
attr_accessor :db
|
4
|
+
def initialize db
|
5
|
+
@db = db
|
6
|
+
end
|
7
|
+
|
8
|
+
def all_docs(limit=100, &block)
|
9
|
+
startkey = nil
|
10
|
+
oldend = nil
|
11
|
+
|
12
|
+
while docrows = request_all_docs(limit+1, startkey)
|
13
|
+
startkey = docrows.last['key']
|
14
|
+
docrows.pop if docrows.length > limit
|
15
|
+
if oldend == startkey
|
16
|
+
break
|
17
|
+
end
|
18
|
+
yield(docrows)
|
19
|
+
oldend = startkey
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
|
24
|
+
# start with no keys
|
25
|
+
startkey = firstkey
|
26
|
+
# lastprocessedkey = nil
|
27
|
+
keepgoing = true
|
28
|
+
|
29
|
+
while keepgoing && viewrows = request_view(view, limit, startkey)
|
30
|
+
startkey = viewrows.first['key']
|
31
|
+
endkey = viewrows.last['key']
|
32
|
+
|
33
|
+
if (startkey == endkey)
|
34
|
+
# we need to rerequest to get a bigger page
|
35
|
+
# so we know we have all the rows for that key
|
36
|
+
viewrows = @db.view(view, :key => startkey)['rows']
|
37
|
+
# we need to do an offset thing to find the next startkey
|
38
|
+
# otherwise we just get stuck
|
39
|
+
lastdocid = viewrows.last['id']
|
40
|
+
fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
|
41
|
+
|
42
|
+
newendkey = fornextloop.last['key']
|
43
|
+
if (newendkey == endkey)
|
44
|
+
keepgoing = false
|
45
|
+
else
|
46
|
+
startkey = newendkey
|
47
|
+
end
|
48
|
+
rows = viewrows
|
49
|
+
else
|
50
|
+
rows = []
|
51
|
+
for r in viewrows
|
52
|
+
if (lastkey && r['key'] == lastkey)
|
53
|
+
keepgoing = false
|
54
|
+
break
|
55
|
+
end
|
56
|
+
break if (r['key'] == endkey)
|
57
|
+
rows << r
|
58
|
+
end
|
59
|
+
startkey = endkey
|
60
|
+
end
|
61
|
+
|
62
|
+
key = :begin
|
63
|
+
values = []
|
64
|
+
|
65
|
+
rows.each do |r|
|
66
|
+
if key != r['key']
|
67
|
+
# we're on a new key, yield the old first and then reset
|
68
|
+
yield(key, values) if key != :begin
|
69
|
+
key = r['key']
|
70
|
+
values = []
|
71
|
+
end
|
72
|
+
# keep accumulating
|
73
|
+
values << r['value']
|
74
|
+
end
|
75
|
+
yield(key, values)
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def request_all_docs limit, startkey = nil
|
83
|
+
opts = {}
|
84
|
+
opts[:limit] = limit if limit
|
85
|
+
opts[:startkey] = startkey if startkey
|
86
|
+
results = @db.documents(opts)
|
87
|
+
rows = results['rows']
|
88
|
+
rows unless rows.length == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_view view, limit = nil, startkey = nil, endkey = nil
|
92
|
+
opts = {}
|
93
|
+
opts[:limit] = limit if limit
|
94
|
+
opts[:startkey] = startkey if startkey
|
95
|
+
opts[:endkey] = endkey if endkey
|
96
|
+
|
97
|
+
results = @db.view(view, opts)
|
98
|
+
rows = results['rows']
|
99
|
+
rows unless rows.length == 0
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Streamer
|
3
|
+
attr_accessor :db
|
4
|
+
def initialize db
|
5
|
+
@db = db
|
6
|
+
end
|
7
|
+
|
8
|
+
# Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
|
9
|
+
def view name, params = nil, &block
|
10
|
+
urlst = if /^_/.match(name) then
|
11
|
+
"#{@db.root}/#{name}"
|
12
|
+
else
|
13
|
+
name = name.split('/')
|
14
|
+
dname = name.shift
|
15
|
+
vname = name.join('/')
|
16
|
+
"#{@db.root}/_design/#{dname}/_view/#{vname}"
|
17
|
+
end
|
18
|
+
url = CouchRest.paramify_url urlst, params
|
19
|
+
# puts "stream #{url}"
|
20
|
+
first = nil
|
21
|
+
IO.popen("curl --silent \"#{url}\"") do |view|
|
22
|
+
first = view.gets # discard header
|
23
|
+
while line = view.gets
|
24
|
+
row = parse_line(line)
|
25
|
+
block.call row unless row.nil? # last line "}]" discarded
|
26
|
+
end
|
27
|
+
end
|
28
|
+
parse_first(first)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_line line
|
34
|
+
return nil unless line
|
35
|
+
if /(\{.*\}),?/.match(line.chomp)
|
36
|
+
JSON.parse($1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_first first
|
41
|
+
return nil unless first
|
42
|
+
parts = first.split(',')
|
43
|
+
parts.pop
|
44
|
+
line = parts.join(',')
|
45
|
+
JSON.parse("#{line}}")
|
46
|
+
rescue
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'couchrest/helper/streamer'
|
2
|
+
module CouchRest
|
3
|
+
class Upgrade
|
4
|
+
attr_accessor :olddb, :newdb, :dbname
|
5
|
+
def initialize dbname, old_couch, new_couch
|
6
|
+
@dbname = dbname
|
7
|
+
@olddb = old_couch.database dbname
|
8
|
+
@newdb = new_couch.database!(dbname)
|
9
|
+
@bulk_docs = []
|
10
|
+
end
|
11
|
+
def clone!
|
12
|
+
puts "#{dbname} - #{olddb.info['doc_count']} docs"
|
13
|
+
streamer = CouchRest::Streamer.new(olddb)
|
14
|
+
streamer.view("_all_docs_by_seq") do |row|
|
15
|
+
load_row_docs(row) if row
|
16
|
+
maybe_flush_bulks
|
17
|
+
end
|
18
|
+
flush_bulks!
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def maybe_flush_bulks
|
24
|
+
flush_bulks! if (@bulk_docs.length > 99)
|
25
|
+
end
|
26
|
+
|
27
|
+
def flush_bulks!
|
28
|
+
url = CouchRest.paramify_url "#{@newdb.uri}/_bulk_docs", {:all_or_nothing => true}
|
29
|
+
puts "posting #{@bulk_docs.length} bulk docs to #{url}"
|
30
|
+
begin
|
31
|
+
CouchRest.post url, {:docs => @bulk_docs}
|
32
|
+
@bulk_docs = []
|
33
|
+
rescue Exception => e
|
34
|
+
puts e.response
|
35
|
+
raise e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_row_docs(row)
|
40
|
+
results = @olddb.get(row["id"], {:open_revs => "all", :attachments => true})
|
41
|
+
results.select{|r|r["ok"]}.each do |r|
|
42
|
+
doc = r["ok"]
|
43
|
+
if /^_/.match(doc["_id"]) && !/^_design/.match(doc["_id"])
|
44
|
+
puts "invalid docid #{doc["_id"]} -- trimming"
|
45
|
+
doc["_id"] = doc["_id"].sub('_','')
|
46
|
+
end
|
47
|
+
doc.delete('_rev')
|
48
|
+
@bulk_docs << doc
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# Jonathan D. Stott <jonathan.stott@gmail.com>
|
3
|
+
module CouchRest
|
4
|
+
module JsonResponse
|
5
|
+
attr_accessor :headers, :raw
|
6
|
+
def JsonResponse.create(body)
|
7
|
+
result = JSON.parse(body)
|
8
|
+
result.extend(JsonResponse)
|
9
|
+
result.headers = body.headers
|
10
|
+
result.raw = body.to_s
|
11
|
+
result
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
####################################
|
2
|
+
# USAGE
|
3
|
+
#
|
4
|
+
# in your rack.rb file
|
5
|
+
# require this file and then:
|
6
|
+
#
|
7
|
+
# couch = CouchRest.new
|
8
|
+
# LOG_DB = couch.database!('couchrest-logger')
|
9
|
+
# use CouchRest::Logger, LOG_DB
|
10
|
+
#
|
11
|
+
# Note:
|
12
|
+
# to require just this middleware, if you have the gem installed do:
|
13
|
+
# require 'couchrest/middlewares/logger'
|
14
|
+
#
|
15
|
+
# For log processing examples, see examples at the bottom of this file
|
16
|
+
|
17
|
+
module CouchRest
|
18
|
+
class Logger
|
19
|
+
|
20
|
+
def self.log
|
21
|
+
Thread.current["couchrest.logger"] ||= {:queries => []}
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(app, db=nil)
|
25
|
+
@app = app
|
26
|
+
@db = db
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.record(log_info)
|
30
|
+
log[:queries] << log_info
|
31
|
+
end
|
32
|
+
|
33
|
+
def log
|
34
|
+
Thread.current["couchrest.logger"] ||= {:queries => []}
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset_log
|
38
|
+
Thread.current["couchrest.logger"] = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(env)
|
42
|
+
reset_log
|
43
|
+
log['started_at'] = Time.now
|
44
|
+
log['env'] = env
|
45
|
+
log['url'] = 'http://' + env['HTTP_HOST'] + env['REQUEST_URI']
|
46
|
+
response = @app.call(env)
|
47
|
+
log['ended_at'] = Time.now
|
48
|
+
log['duration'] = log['ended_at'] - log['started_at']
|
49
|
+
# let's report the log in a different thread so we don't slow down the app
|
50
|
+
@db ? Thread.new(@db, log){|db, rlog| db.save_doc(rlog);} : p(log.inspect)
|
51
|
+
response
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# inject our logger into key RestClient methods
|
57
|
+
module RestClient
|
58
|
+
|
59
|
+
def self.get(uri, headers=nil)
|
60
|
+
start_query = Time.now
|
61
|
+
log = {:method => :get, :uri => uri, :headers => headers}
|
62
|
+
response = super(uri, headers=nil)
|
63
|
+
end_query = Time.now
|
64
|
+
log[:duration] = (end_query - start_query)
|
65
|
+
CouchRest::Logger.record(log)
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.post(uri, payload, headers=nil)
|
70
|
+
start_query = Time.now
|
71
|
+
log = {:method => :post, :uri => uri, :payload => (payload ? (JSON.load(payload) rescue 'parsing error') : nil), :headers => headers}
|
72
|
+
response = super(uri, payload, headers=nil)
|
73
|
+
end_query = Time.now
|
74
|
+
log[:duration] = (end_query - start_query)
|
75
|
+
CouchRest::Logger.record(log)
|
76
|
+
response
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.put(uri, payload, headers=nil)
|
80
|
+
start_query = Time.now
|
81
|
+
log = {:method => :put, :uri => uri, :payload => (payload ? (JSON.load(payload) rescue 'parsing error') : nil), :headers => headers}
|
82
|
+
response = super(uri, payload, headers=nil)
|
83
|
+
end_query = Time.now
|
84
|
+
log[:duration] = (end_query - start_query)
|
85
|
+
CouchRest::Logger.record(log)
|
86
|
+
response
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.delete(uri, headers=nil)
|
90
|
+
start_query = Time.now
|
91
|
+
log = {:method => :delete, :uri => uri, :headers => headers}
|
92
|
+
response = super(uri, headers=nil)
|
93
|
+
end_query = Time.now
|
94
|
+
log[:duration] = (end_query - start_query)
|
95
|
+
CouchRest::Logger.record(log)
|
96
|
+
response
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Advanced usage example
|
103
|
+
#
|
104
|
+
#
|
105
|
+
# # DB VIEWS
|
106
|
+
# by_url = {
|
107
|
+
# :map =>
|
108
|
+
# "function(doc) {
|
109
|
+
# if(doc['url']){ emit(doc['url'], 1) };
|
110
|
+
# }",
|
111
|
+
# :reduce =>
|
112
|
+
# 'function (key, values, rereduce) {
|
113
|
+
# return(sum(values));
|
114
|
+
# };'
|
115
|
+
# }
|
116
|
+
# req_duration = {
|
117
|
+
# :map =>
|
118
|
+
# "function(doc) {
|
119
|
+
# if(doc['duration']){ emit(doc['url'], doc['duration']) };
|
120
|
+
# }",
|
121
|
+
# :reduce =>
|
122
|
+
# 'function (key, values, rereduce) {
|
123
|
+
# return(sum(values)/values.length);
|
124
|
+
# };'
|
125
|
+
# }
|
126
|
+
#
|
127
|
+
# query_duration = {
|
128
|
+
# :map =>
|
129
|
+
# "function(doc) {
|
130
|
+
# if(doc['queries']){
|
131
|
+
# doc.queries.forEach(function(query){
|
132
|
+
# if(query['duration'] && query['method']){
|
133
|
+
# emit(query['method'], query['duration'])
|
134
|
+
# }
|
135
|
+
# });
|
136
|
+
# };
|
137
|
+
# }" ,
|
138
|
+
# :reduce =>
|
139
|
+
# 'function (key, values, rereduce) {
|
140
|
+
# return(sum(values)/values.length);
|
141
|
+
# };'
|
142
|
+
# }
|
143
|
+
#
|
144
|
+
# action_queries = {
|
145
|
+
# :map =>
|
146
|
+
# "function(doc) {
|
147
|
+
# if(doc['queries']){
|
148
|
+
# emit(doc['url'], doc['queries'].length)
|
149
|
+
# };
|
150
|
+
# }",
|
151
|
+
# :reduce =>
|
152
|
+
# 'function (key, values, rereduce) {
|
153
|
+
# return(sum(values)/values.length);
|
154
|
+
# };'
|
155
|
+
# }
|
156
|
+
#
|
157
|
+
# action_time_spent_in_db = {
|
158
|
+
# :map =>
|
159
|
+
# "function(doc) {
|
160
|
+
# if(doc['queries']){
|
161
|
+
# var totalDuration = 0;
|
162
|
+
# doc.queries.forEach(function(query){
|
163
|
+
# totalDuration += query['duration']
|
164
|
+
# })
|
165
|
+
# emit(doc['url'], totalDuration)
|
166
|
+
# };
|
167
|
+
# }",
|
168
|
+
# :reduce =>
|
169
|
+
# 'function (key, values, rereduce) {
|
170
|
+
# return(sum(values)/values.length);
|
171
|
+
# };'
|
172
|
+
# }
|
173
|
+
#
|
174
|
+
# show_queries = %Q~function(doc, req) {
|
175
|
+
# var body = ""
|
176
|
+
# body += "<h1>" + doc['url'] + "</h1>"
|
177
|
+
# body += "<h2>Request duration in seconds: " + doc['duration'] + "</h2>"
|
178
|
+
# body += "<h3>" + doc['queries'].length + " queries</h3><ul>"
|
179
|
+
# if (doc.queries){
|
180
|
+
# doc.queries.forEach(function(query){
|
181
|
+
# body += "<li>"+ query['uri'] +"</li>"
|
182
|
+
# });
|
183
|
+
# };
|
184
|
+
# body += "</ul>"
|
185
|
+
# if(doc){ return { body: body} }
|
186
|
+
# }~
|
187
|
+
#
|
188
|
+
#
|
189
|
+
# couch = CouchRest.new
|
190
|
+
# LOG_DB = couch.database!('couchrest-logger')
|
191
|
+
# design_doc = LOG_DB.get("_design/stats") rescue nil
|
192
|
+
# LOG_DB.delete_doc design_doc rescue nil
|
193
|
+
# LOG_DB.save_doc({
|
194
|
+
# "_id" => "_design/stats",
|
195
|
+
# :views => {
|
196
|
+
# :by_url => by_url,
|
197
|
+
# :request_duration => req_duration,
|
198
|
+
# :query_duration => query_duration,
|
199
|
+
# :action_queries => action_queries,
|
200
|
+
# :action_time_spent_in_db => action_time_spent_in_db
|
201
|
+
# },
|
202
|
+
# :shows => {
|
203
|
+
# :queries => show_queries
|
204
|
+
# }
|
205
|
+
# })
|
206
|
+
#
|
207
|
+
# module CouchRest
|
208
|
+
# class Logger
|
209
|
+
#
|
210
|
+
# def self.roundup(value)
|
211
|
+
# begin
|
212
|
+
# value = Float(value)
|
213
|
+
# (value * 100).round.to_f / 100
|
214
|
+
# rescue
|
215
|
+
# value
|
216
|
+
# end
|
217
|
+
# end
|
218
|
+
#
|
219
|
+
# # Usage example:
|
220
|
+
# # CouchRest::Logger.average_request_duration(LOG_DB)['rows'].first['value']
|
221
|
+
# def self.average_request_duration(db)
|
222
|
+
# raw = db.view('stats/request_duration', :reduce => true)
|
223
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# def self.average_query_duration(db)
|
227
|
+
# raw = db.view('stats/query_duration', :reduce => true)
|
228
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
229
|
+
# end
|
230
|
+
#
|
231
|
+
# def self.average_get_query_duration(db)
|
232
|
+
# raw = db.view('stats/query_duration', :key => 'get', :reduce => true)
|
233
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
# def self.average_post_query_duration(db)
|
237
|
+
# raw = db.view('stats/query_duration', :key => 'post', :reduce => true)
|
238
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
239
|
+
# end
|
240
|
+
#
|
241
|
+
# def self.average_queries_per_action(db)
|
242
|
+
# raw = db.view('stats/action_queries', :reduce => true)
|
243
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# def self.average_db_time_per_action(db)
|
247
|
+
# raw = db.view('stats/action_time_spent_in_db', :reduce => true)
|
248
|
+
# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# def self.stats(db)
|
252
|
+
# Thread.new(db){|db|
|
253
|
+
# puts "=== STATS ===\n"
|
254
|
+
# puts "average request duration: #{average_request_duration(db)}\n"
|
255
|
+
# puts "average query duration: #{average_query_duration(db)}\n"
|
256
|
+
# puts "average queries per action : #{average_queries_per_action(db)}\n"
|
257
|
+
# puts "average time spent in DB (per action): #{average_db_time_per_action(db)}\n"
|
258
|
+
# puts "===============\n"
|
259
|
+
# }
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# end
|
263
|
+
# end
|