cehoffman-acts_as_ferret 0.4.4

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.
Files changed (162) hide show
  1. data/LICENSE +20 -0
  2. data/README +68 -0
  3. data/bin/aaf_install +23 -0
  4. data/config/ferret_server.yml +24 -0
  5. data/doc/README.win32 +23 -0
  6. data/doc/demo/README +154 -0
  7. data/doc/demo/README_DEMO +23 -0
  8. data/doc/demo/Rakefile +10 -0
  9. data/doc/demo/app/controllers/admin/backend_controller.rb +14 -0
  10. data/doc/demo/app/controllers/admin_area_controller.rb +4 -0
  11. data/doc/demo/app/controllers/application.rb +5 -0
  12. data/doc/demo/app/controllers/contents_controller.rb +49 -0
  13. data/doc/demo/app/controllers/searches_controller.rb +8 -0
  14. data/doc/demo/app/helpers/admin/backend_helper.rb +2 -0
  15. data/doc/demo/app/helpers/application_helper.rb +3 -0
  16. data/doc/demo/app/helpers/content_helper.rb +2 -0
  17. data/doc/demo/app/helpers/search_helper.rb +2 -0
  18. data/doc/demo/app/models/comment.rb +48 -0
  19. data/doc/demo/app/models/content.rb +12 -0
  20. data/doc/demo/app/models/content_base.rb +28 -0
  21. data/doc/demo/app/models/search.rb +19 -0
  22. data/doc/demo/app/models/shared_index1.rb +3 -0
  23. data/doc/demo/app/models/shared_index2.rb +3 -0
  24. data/doc/demo/app/models/special_content.rb +3 -0
  25. data/doc/demo/app/models/stats.rb +20 -0
  26. data/doc/demo/app/views/admin/backend/search.rhtml +18 -0
  27. data/doc/demo/app/views/contents/_form.rhtml +10 -0
  28. data/doc/demo/app/views/contents/edit.rhtml +9 -0
  29. data/doc/demo/app/views/contents/index.rhtml +24 -0
  30. data/doc/demo/app/views/contents/new.rhtml +8 -0
  31. data/doc/demo/app/views/contents/show.rhtml +8 -0
  32. data/doc/demo/app/views/layouts/application.html.erb +17 -0
  33. data/doc/demo/app/views/searches/_content.html.erb +2 -0
  34. data/doc/demo/app/views/searches/search.html.erb +20 -0
  35. data/doc/demo/config/boot.rb +109 -0
  36. data/doc/demo/config/database.yml +38 -0
  37. data/doc/demo/config/environment.rb +69 -0
  38. data/doc/demo/config/environments/development.rb +16 -0
  39. data/doc/demo/config/environments/production.rb +19 -0
  40. data/doc/demo/config/environments/test.rb +21 -0
  41. data/doc/demo/config/ferret_server.yml +18 -0
  42. data/doc/demo/config/lighttpd.conf +40 -0
  43. data/doc/demo/config/routes.rb +9 -0
  44. data/doc/demo/db/development_structure.sql +15 -0
  45. data/doc/demo/db/migrate/001_initial_migration.rb +18 -0
  46. data/doc/demo/db/migrate/002_add_type_to_contents.rb +9 -0
  47. data/doc/demo/db/migrate/003_create_shared_index1s.rb +11 -0
  48. data/doc/demo/db/migrate/004_create_shared_index2s.rb +11 -0
  49. data/doc/demo/db/migrate/005_special_field.rb +9 -0
  50. data/doc/demo/db/migrate/006_create_stats.rb +15 -0
  51. data/doc/demo/db/schema.sql +18 -0
  52. data/doc/demo/doc/README_FOR_APP +2 -0
  53. data/doc/demo/doc/howto.txt +70 -0
  54. data/doc/demo/public/.htaccess +40 -0
  55. data/doc/demo/public/404.html +8 -0
  56. data/doc/demo/public/500.html +8 -0
  57. data/doc/demo/public/dispatch.cgi +10 -0
  58. data/doc/demo/public/dispatch.fcgi +24 -0
  59. data/doc/demo/public/dispatch.rb +10 -0
  60. data/doc/demo/public/favicon.ico +0 -0
  61. data/doc/demo/public/images/rails.png +0 -0
  62. data/doc/demo/public/index.html +277 -0
  63. data/doc/demo/public/robots.txt +1 -0
  64. data/doc/demo/public/stylesheets/scaffold.css +74 -0
  65. data/doc/demo/script/about +3 -0
  66. data/doc/demo/script/breakpointer +3 -0
  67. data/doc/demo/script/console +3 -0
  68. data/doc/demo/script/destroy +3 -0
  69. data/doc/demo/script/ferret_server +10 -0
  70. data/doc/demo/script/generate +3 -0
  71. data/doc/demo/script/performance/benchmarker +3 -0
  72. data/doc/demo/script/performance/profiler +3 -0
  73. data/doc/demo/script/plugin +3 -0
  74. data/doc/demo/script/process/inspector +3 -0
  75. data/doc/demo/script/process/reaper +3 -0
  76. data/doc/demo/script/process/spawner +3 -0
  77. data/doc/demo/script/process/spinner +3 -0
  78. data/doc/demo/script/runner +3 -0
  79. data/doc/demo/script/server +3 -0
  80. data/doc/demo/test/fixtures/comments.yml +12 -0
  81. data/doc/demo/test/fixtures/contents.yml +13 -0
  82. data/doc/demo/test/fixtures/remote_contents.yml +9 -0
  83. data/doc/demo/test/fixtures/shared_index1s.yml +7 -0
  84. data/doc/demo/test/fixtures/shared_index2s.yml +7 -0
  85. data/doc/demo/test/functional/admin/backend_controller_test.rb +35 -0
  86. data/doc/demo/test/functional/contents_controller_test.rb +81 -0
  87. data/doc/demo/test/functional/searches_controller_test.rb +71 -0
  88. data/doc/demo/test/smoke/drb_smoke_test.rb +321 -0
  89. data/doc/demo/test/smoke/process_stats.rb +21 -0
  90. data/doc/demo/test/test_helper.rb +30 -0
  91. data/doc/demo/test/unit/comment_test.rb +217 -0
  92. data/doc/demo/test/unit/content_test.rb +705 -0
  93. data/doc/demo/test/unit/ferret_result_test.rb +24 -0
  94. data/doc/demo/test/unit/multi_index_test.rb +329 -0
  95. data/doc/demo/test/unit/remote_index_test.rb +23 -0
  96. data/doc/demo/test/unit/shared_index1_test.rb +108 -0
  97. data/doc/demo/test/unit/shared_index2_test.rb +13 -0
  98. data/doc/demo/test/unit/sort_test.rb +21 -0
  99. data/doc/demo/test/unit/special_content_test.rb +25 -0
  100. data/doc/demo/vendor/plugins/will_paginate/LICENSE +18 -0
  101. data/doc/demo/vendor/plugins/will_paginate/README +108 -0
  102. data/doc/demo/vendor/plugins/will_paginate/Rakefile +23 -0
  103. data/doc/demo/vendor/plugins/will_paginate/init.rb +21 -0
  104. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +45 -0
  105. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb +44 -0
  106. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/finder.rb +159 -0
  107. data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb +95 -0
  108. data/doc/demo/vendor/plugins/will_paginate/test/array_pagination_test.rb +23 -0
  109. data/doc/demo/vendor/plugins/will_paginate/test/boot.rb +27 -0
  110. data/doc/demo/vendor/plugins/will_paginate/test/console +10 -0
  111. data/doc/demo/vendor/plugins/will_paginate/test/finder_test.rb +219 -0
  112. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/admin.rb +3 -0
  113. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/companies.yml +24 -0
  114. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/company.rb +23 -0
  115. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developer.rb +11 -0
  116. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml +13 -0
  117. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/project.rb +4 -0
  118. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/projects.yml +7 -0
  119. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/replies.yml +20 -0
  120. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/reply.rb +5 -0
  121. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/schema.sql +44 -0
  122. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topic.rb +19 -0
  123. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topics.yml +30 -0
  124. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/user.rb +2 -0
  125. data/doc/demo/vendor/plugins/will_paginate/test/fixtures/users.yml +35 -0
  126. data/doc/demo/vendor/plugins/will_paginate/test/helper.rb +42 -0
  127. data/doc/demo/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb +64 -0
  128. data/doc/demo/vendor/plugins/will_paginate/test/lib/load_fixtures.rb +10 -0
  129. data/doc/demo/vendor/plugins/will_paginate/test/pagination_test.rb +136 -0
  130. data/doc/monit-example +22 -0
  131. data/init.rb +24 -0
  132. data/install.rb +18 -0
  133. data/lib/act_methods.rb +147 -0
  134. data/lib/acts_as_ferret.rb +584 -0
  135. data/lib/ar_mysql_auto_reconnect_patch.rb +41 -0
  136. data/lib/blank_slate.rb +53 -0
  137. data/lib/bulk_indexer.rb +38 -0
  138. data/lib/class_methods.rb +270 -0
  139. data/lib/ferret_extensions.rb +188 -0
  140. data/lib/ferret_find_methods.rb +141 -0
  141. data/lib/ferret_result.rb +53 -0
  142. data/lib/ferret_server.rb +238 -0
  143. data/lib/index.rb +99 -0
  144. data/lib/instance_methods.rb +171 -0
  145. data/lib/local_index.rb +205 -0
  146. data/lib/more_like_this.rb +217 -0
  147. data/lib/multi_index.rb +126 -0
  148. data/lib/rdig_adapter.rb +148 -0
  149. data/lib/remote_functions.rb +23 -0
  150. data/lib/remote_index.rb +54 -0
  151. data/lib/remote_multi_index.rb +20 -0
  152. data/lib/search_results.rb +50 -0
  153. data/lib/server_manager.rb +58 -0
  154. data/lib/unix_daemon.rb +64 -0
  155. data/lib/without_ar.rb +52 -0
  156. data/rakefile +141 -0
  157. data/recipes/aaf_recipes.rb +114 -0
  158. data/script/ferret_daemon +94 -0
  159. data/script/ferret_server +10 -0
  160. data/script/ferret_service +178 -0
  161. data/tasks/ferret.rake +22 -0
  162. metadata +258 -0
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/generate'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/benchmarker'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/profiler'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/plugin'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/inspector'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/reaper'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/spawner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/spinner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/runner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/server'
@@ -0,0 +1,12 @@
1
+ first:
2
+ id: 1
3
+ content: comment from fixture
4
+ content_id: 1
5
+ another:
6
+ id: 2
7
+ content: second comment from fixture
8
+ content_id: 1
9
+ comment_for_c2:
10
+ id: 3
11
+ content: regarding content 2
12
+ content_id: 2
@@ -0,0 +1,13 @@
1
+ first:
2
+ id: 1
3
+ title: My Title
4
+ description: A useless description
5
+ type: Content
6
+ another:
7
+ id: 2
8
+ type: Content
9
+ special:
10
+ id: 3
11
+ title: single table inheritance
12
+ special: special field
13
+ type: SpecialContent
@@ -0,0 +1,9 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ first:
3
+ id: 1
4
+ title: title of item one
5
+ content: content of item one
6
+ another:
7
+ id: 2
8
+ title: title of item two
9
+ content: content of item two
@@ -0,0 +1,7 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ first:
3
+ id: 1
4
+ name: first one
5
+ another:
6
+ id: 2
7
+ name: another one
@@ -0,0 +1,7 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ first:
3
+ id: 1
4
+ name: first two
5
+ another:
6
+ id: 2
7
+ name: another two
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+ require 'admin/backend_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class Admin::BackendController; def rescue_action(e) raise e end; end
6
+
7
+ class Admin::BackendControllerTest < Test::Unit::TestCase
8
+ def setup
9
+ @controller = Admin::BackendController.new
10
+ @request = ActionController::TestRequest.new
11
+ @response = ActionController::TestResponse.new
12
+ Content.destroy_all
13
+ Content.create(:title => 'my title', :description => 'a little bit of content')
14
+ end
15
+
16
+ def teardown
17
+ Content.destroy_all
18
+ end
19
+
20
+ def test_search
21
+ get :search
22
+ assert_response :success
23
+ assert_template 'search'
24
+ assert_nil assigns(:results)
25
+
26
+ post :search, :query => 'title'
27
+ assert_template 'search'
28
+ assert_equal 1, assigns(:results).size
29
+
30
+ post :search, :query => 'monkey'
31
+ assert_template 'search'
32
+ assert assigns(:results).empty?
33
+
34
+ end
35
+ end
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'contents_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class ContentsController; def rescue_action(e) raise e end; end
6
+
7
+ class ContentsControllerTest < Test::Unit::TestCase
8
+ fixtures :contents
9
+
10
+ def setup
11
+ @controller = ContentsController.new
12
+ @request = ActionController::TestRequest.new
13
+ @response = ActionController::TestResponse.new
14
+ end
15
+
16
+ def test_index
17
+ get :index
18
+ assert_response :success
19
+ assert_template 'index'
20
+ assert_not_nil assigns(:contents)
21
+ end
22
+
23
+ def test_show
24
+ get :show, :id => 1
25
+
26
+ assert_response :success
27
+ assert_template 'show'
28
+
29
+ assert_not_nil assigns(:content)
30
+ assert assigns(:content).valid?
31
+ end
32
+
33
+ def test_new
34
+ get :new
35
+
36
+ assert_response :success
37
+ assert_template 'new'
38
+
39
+ assert_not_nil assigns(:content)
40
+ end
41
+
42
+ def test_create
43
+ num_contents = Content.count
44
+
45
+ post :create, :content => {}
46
+
47
+ assert_response :redirect
48
+ assert_redirected_to contents_url
49
+
50
+ assert_equal num_contents + 1, Content.count
51
+ end
52
+
53
+ def test_edit
54
+ get :edit, :id => 1
55
+
56
+ assert_response :success
57
+ assert_template 'edit'
58
+
59
+ assert_not_nil assigns(:content)
60
+ assert assigns(:content).valid?
61
+ end
62
+
63
+ def test_update
64
+ post :update, :id => 1
65
+ assert_response :redirect
66
+ assert_redirected_to :action => 'show', :id => 1
67
+ end
68
+
69
+ def test_destroy
70
+ assert_not_nil Content.find(1)
71
+
72
+ post :destroy, :id => 1
73
+ assert_response :redirect
74
+ assert_redirected_to :action => 'list'
75
+
76
+ assert_raise(ActiveRecord::RecordNotFound) {
77
+ Content.find(1)
78
+ }
79
+ end
80
+
81
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'searches_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class SearchesController; def rescue_action(e) raise e end; end
6
+
7
+ class SearchesControllerTest < Test::Unit::TestCase
8
+ fixtures :contents
9
+
10
+ def setup
11
+ @controller = SearchesController.new
12
+ @request = ActionController::TestRequest.new
13
+ @response = ActionController::TestResponse.new
14
+ ContentBase.rebuild_index
15
+ end
16
+
17
+ def test_search
18
+ get :search
19
+ assert_template 'search'
20
+ assert_response :success
21
+ assert_nil assigns(:results)
22
+ end
23
+
24
+ def test_search
25
+ get :search, :q => 'title'
26
+ assert_template 'search'
27
+ assert_equal 1, assigns(:results).total_hits
28
+ assert_equal 1, assigns(:results).size
29
+
30
+ get :search, :q => 'monkey'
31
+ assert_template 'search'
32
+ assert assigns(:results).empty?
33
+
34
+ # check that model changes are picked up by the searcher (searchers have to
35
+ # be reopened to reflect changes done to the index)
36
+ # wait for the searcher to age a bit (it seems fs timestamp resolution is
37
+ # only 1 sec)
38
+ sleep 1
39
+ Content.create :title => 'another content object', :description => 'description goes hers'
40
+ get :search, :q => 'another'
41
+ assert_template 'search'
42
+ assert_equal 1, assigns(:results).total_hits
43
+ assert_equal 1, assigns(:results).size
44
+
45
+ end
46
+
47
+ def test_pagination
48
+ Content.destroy_all
49
+ 30.times do |i|
50
+ Content.create! :title => "title of Content #{i}", :description => "#{i}"
51
+ end
52
+ get :search, :q => 'title'
53
+ r = assigns(:results)
54
+ assert_equal 30, r.total_hits
55
+ assert_equal 10, r.size
56
+ assert_equal "title of Content 0", r.first.title
57
+ assert_equal "title of Content 9", r.last.title
58
+ assert_equal 1, r.current_page
59
+ assert_equal 3, r.page_count
60
+
61
+ get :search, :q => 'title', :page => 2
62
+ r = assigns(:results)
63
+ assert_equal 30, r.total_hits
64
+ assert_equal 10, r.size
65
+ assert_equal "title of Content 10", r.first.title
66
+ assert_equal "title of Content 19", r.last.title
67
+ assert_equal 2, r.current_page
68
+ assert_equal 3, r.page_count
69
+ end
70
+
71
+ end
@@ -0,0 +1,321 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+ require 'gruff'
4
+
5
+ # Simple smoke test for the DRb server
6
+ # usage:
7
+ #
8
+ # # start the DRb server
9
+ # script/ferret_server -e test start
10
+ #
11
+ # # run the script
12
+ # AAF_REMOTE=true script/runner -e test test/smoke/drb_smoke_test.rb
13
+
14
+ module DrbSmokeTest
15
+
16
+ RECORDS_PER_PROCESS = 100000
17
+ NUM_PROCESSES = 10 # should be an even number
18
+ NUM_DOCS = 50
19
+ NUM_TERMS = 600
20
+ START_TIME = Time.now
21
+
22
+ class Words
23
+ DICTIONARY = '/usr/share/dict/words'
24
+ def initialize
25
+ @words = []
26
+ File.open(DICTIONARY) do |file|
27
+ file.each_line do |word|
28
+ @words << word.strip unless word =~ /'/
29
+ end
30
+ end
31
+ end
32
+
33
+ def to_s
34
+ "#{@words.size} words"
35
+ end
36
+
37
+ def random_word
38
+ @words[rand(@words.size)]
39
+ end
40
+ end
41
+
42
+ puts "compiling sample documents..."
43
+ WORDS = Words.new
44
+ puts WORDS
45
+ DOCUMENTS = []
46
+
47
+ NUM_DOCS.times do
48
+ doc = ''
49
+ NUM_TERMS.times { doc << WORDS.random_word << ' ' }
50
+ DOCUMENTS << doc
51
+ end
52
+
53
+ def self.random_document
54
+ DOCUMENTS[rand(DOCUMENTS.size)]
55
+ end
56
+
57
+ puts "built #{NUM_DOCS} documents with an avg. size of #{DOCUMENTS.join.size / NUM_DOCS} Bytes."
58
+
59
+ class Monitor
60
+ class << self
61
+ def count_connections
62
+ res = Content.connection.execute("show status where variable_name = 'Threads_connected'")
63
+ if res
64
+ res.fetch_row.last
65
+ else
66
+ "error getting connection count"
67
+ end
68
+ end
69
+ def writers_running?
70
+ Dir['*.finished'].size < (NUM_PROCESSES/2)
71
+ end
72
+ def running?
73
+ Dir['*.finished'].size < NUM_PROCESSES
74
+ end
75
+ end
76
+ end
77
+
78
+ class WorkerBase
79
+ def initialize(id)
80
+ @id = id
81
+ end
82
+
83
+ # time since startup in msec
84
+ def get_time
85
+ ((Time.now - START_TIME)*1000).to_i
86
+ end
87
+
88
+ def log(data)
89
+ data << get_time
90
+ @logfile << data.join(',') << "\n"
91
+ end
92
+
93
+ def log_finished
94
+ File.open("#{@id}.finished", 'w') do |f|
95
+ f << "finished at #{Time.now}\n"
96
+ end
97
+ end
98
+
99
+ def clear_logs
100
+ FileUtils.rm_f "#{@id}.*"
101
+ end
102
+
103
+ def run
104
+ File.open("#{self.class.prefix}_#{@id}.log",'w') do |f|
105
+ clear_logs
106
+ sleep 1 # allow other processes to get ready
107
+ @logfile = f
108
+ do_run
109
+ log_finished
110
+ puts "#{@id} finished"
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ class Writer < WorkerBase
117
+ def self.prefix; 'writer' end
118
+ def do_run
119
+ log_interval = RECORDS_PER_PROCESS / 100
120
+ RECORDS_PER_PROCESS.times do |i|
121
+ log create_record(i)
122
+ if i % log_interval == 0
123
+ # log progress
124
+ puts "#{@id}: #{i} records indexed"
125
+ end
126
+ end
127
+ end
128
+
129
+ def create_record(i)
130
+ time = Benchmark.realtime do
131
+ Content.create! :title => "record #{@id} / #{i}", :description => DrbSmokeTest::random_document
132
+ end
133
+ [ time ]
134
+ end
135
+ end
136
+
137
+ class Searcher < WorkerBase
138
+ def self.prefix; 'searcher' end
139
+ def do_run
140
+ while Monitor::writers_running?
141
+ # search with concurrent writes
142
+ log do_search
143
+ end
144
+ t = Time.now
145
+ while (Time.now - t) < 2.minutes
146
+ # the writers have finished, now hammer the server with searches for another 5 minutes
147
+ log do_search
148
+ end
149
+ end
150
+
151
+ # run a search and log it's results.
152
+ # Search is done with a query consisting of the term 'findme'
153
+ # (which is guaranteed to yield 20 results) and a random term from
154
+ # the word list, ORed together
155
+ def do_search
156
+ result = nil
157
+ query = "findme OR #{WORDS.random_word}"
158
+ time = Benchmark.realtime do
159
+ result = Content.find_id_by_contents query
160
+ end
161
+ # time, no of hits
162
+ [ time, result.first, query ]
163
+ end
164
+ end
165
+
166
+ def self.run
167
+ @start = Time.now
168
+
169
+ NUM_PROCESSES.times do |i|
170
+ unless fork
171
+ @id = i
172
+ break
173
+ end
174
+ end
175
+
176
+ if @id
177
+ @id.even? ? Writer.new(@id).run : Searcher.new(@id).run
178
+ else
179
+
180
+ # create some records to search for
181
+ 20.times do |i|
182
+ Content.create! :title => "to find #{i}", :description => ("findme #{i} " << random_document)
183
+ end
184
+
185
+ while Monitor::running?
186
+ puts "open connections: #{Monitor::count_connections}; time elapsed: #{Time.now - @start} seconds"
187
+ sleep 10
188
+ end
189
+ puts "doing the math now..."
190
+ DrbSmokeTest::Stats.new(DrbSmokeTest::Writer::prefix).run
191
+ DrbSmokeTest::Stats.new(DrbSmokeTest::Searcher::prefix).run
192
+ end
193
+ end
194
+
195
+ module Statistics
196
+ def odd?(value)
197
+ value % 2 == 1
198
+ end
199
+
200
+ def median(population)
201
+ if odd?(population.size)
202
+ population[population.size/2]
203
+ else
204
+ mean [ population[population.size/2-1], population[population.size/2] ]
205
+ end
206
+ end
207
+
208
+ def mean(population)
209
+ sum = population.inject(0) { |sum, v| sum + v }
210
+ sum / population.size.to_f
211
+ end
212
+
213
+ # variance and standard_deviation methods from
214
+ # http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/
215
+ def variance(population)
216
+ n = 0
217
+ mean = 0.0
218
+ s = 0.0
219
+ population.each { |x|
220
+ n = n + 1
221
+ delta = x - mean
222
+ mean = mean + (delta / n)
223
+ s = s + delta * (x - mean)
224
+ }
225
+ # if you want to calculate std deviation
226
+ # of a sample change this to "s / (n-1)"
227
+ return s / n
228
+ end
229
+
230
+ # calculate the standard deviation of a population
231
+ # accepts: an array, the population
232
+ # returns: the standard deviation
233
+ def standard_deviation(population)
234
+ Math.sqrt(variance(population))
235
+ rescue
236
+ puts "pop: #{population.inspect}"
237
+ end
238
+ end
239
+
240
+ class Stats
241
+ include Statistics
242
+
243
+ def initialize(prefix)
244
+ @prefix = prefix
245
+ @stats = []
246
+ end
247
+
248
+ def collect_stats
249
+ Dir["#{@prefix}_*.log"].each do |logfile|
250
+ puts logfile
251
+ File.open(logfile) do |f|
252
+ while line = f.gets
253
+ row = line.split(',')
254
+ row[row.size-1] = row.last.to_i
255
+ @stats << row
256
+ end
257
+ end
258
+ end
259
+ puts "#{@stats.size} lines read, now sorting..."
260
+ @stats.sort! { |row1, row2| row1.last <=> row2.last }
261
+ end
262
+
263
+ def with_segments(segment_count)
264
+ t0 = @stats.first.last.to_i
265
+ t1 = @stats.last.last.to_i
266
+ timespan = t1 - t0
267
+ puts "test run took: #{timespan/1000} seconds"
268
+ # we want to draw 1000 points, determine which timespan one point covers
269
+ segment_length = timespan / segment_count
270
+ t = 0
271
+ i = 0
272
+ while t <= t1
273
+ t += segment_length
274
+ segment_stats = []
275
+ while @stats.any? && @stats.first.last.to_i < t
276
+ segment_stats << @stats.shift
277
+ end
278
+ yield segment_stats unless segment_stats.empty?
279
+ end
280
+ end
281
+
282
+ def run
283
+ collect_stats
284
+ segments = []
285
+ with_segments(500) do |segment_stats|
286
+ segments << process_segment(segment_stats)
287
+ end
288
+
289
+ chart("#{@prefix} mean", "#{@prefix.downcase}_mean") do |g|
290
+ g.data :mean, segments.map{ |row| row[0] }
291
+ g.data :stddev, segments.map{ |row| row[1] }
292
+ end
293
+ chart("#{@prefix} median", "#{@prefix.downcase}_median") do |g|
294
+ g.data :median, segments.map{ |row| row[2] }
295
+ end
296
+ end
297
+
298
+ def process_segment(segment)
299
+ times = segment.map{|row|row.first.to_i * 1000}
300
+ [mean(times), standard_deviation(times), median(times), segment.size]
301
+ end
302
+
303
+ def chart(title, fname)
304
+ g = Gruff::Line.new do |g|
305
+ g.title = title
306
+ g.theme = {
307
+ :background_colors => ["#e6e6e6", "#e6e6e6"],
308
+ :colors => ["#ff43a7", '#666666', 'black', 'white', 'grey'],
309
+ :marker_color => "white"
310
+ }
311
+ end
312
+ yield g
313
+ g.write "#{fname}.png"
314
+ end
315
+ end
316
+
317
+ end
318
+
319
+
320
+ DrbSmokeTest::run
321
+