Commit 7aa332b42461c0c3e15ff36b3363eb4fb06e50d4

Authored by Marcin Ciunelis
1 parent dffb8d28
Exists in master and in 1 other branch production

cache notices statistics in problem

app/helpers/application_helper.rb
1 1 module ApplicationHelper
2 2 def message_graph(problem)
3   - create_percentage_table_for(problem) {|notice| notice.message}
  3 + create_percentage_table_for(problem.messages)
4 4 end
5 5  
6 6 def generate_problem_ical(notices)
... ... @@ -35,35 +35,19 @@ module ApplicationHelper
35 35 end
36 36  
37 37 def user_agent_graph(problem)
38   - create_percentage_table_for(problem) {|notice| pretty_user_agent(notice.user_agent)}
39   - end
40   -
41   - def pretty_user_agent(user_agent)
42   - (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}"
  38 + create_percentage_table_for(problem.user_agents)
43 39 end
44 40  
45 41 def tenant_graph(problem)
46   - create_percentage_table_for(problem) {|notice| get_host(notice.request['url'])}
47   - end
48   -
49   - def get_host(url)
50   - begin
51   - uri = url && URI.parse(url)
52   - uri.blank? ? "N/A" : uri.host
53   - rescue URI::InvalidURIError
54   - "N/A"
55   - end
  42 + create_percentage_table_for(problem.hosts)
56 43 end
57 44  
58   -
59   - def create_percentage_table_for(problem, &block)
60   - tallies = tally(problem.notices, &block)
61   - create_percentage_table_from_tallies(tallies, :total => problem.notices.count)
  45 + def create_percentage_table_for(collection)
  46 + create_percentage_table_from_tallies(tally(collection))
62 47 end
63 48  
64   - def tally(collection, &block)
65   - collection.inject({}) do |tallies, item|
66   - value = yield item
  49 + def tally(collection)
  50 + collection.inject({}) do |tallies, value|
67 51 tallies[value] = (tallies[value] || 0) + 1
68 52 tallies
69 53 end
... ...
app/models/notice.rb
... ... @@ -19,7 +19,7 @@ class Notice
19 19 after_create :increase_counter_cache, :cache_attributes_on_problem, :unresolve_problem
20 20 after_create :deliver_notification, :if => :should_notify?
21 21 before_save :sanitize
22   - before_destroy :decrease_counter_cache
  22 + before_destroy :decrease_counter_cache, :remove_cached_attributes_from_problem
23 23  
24 24 validates_presence_of :backtrace, :server_environment, :notifier
25 25  
... ... @@ -33,6 +33,10 @@ class Notice
33 33 agent_string.blank? ? nil : UserAgent.parse(agent_string)
34 34 end
35 35  
  36 + def user_agent_string
  37 + (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}"
  38 + end
  39 +
36 40 def environment_name
37 41 server_environment['server-environment'] || server_environment['environment-name']
38 42 end
... ... @@ -59,6 +63,17 @@ class Notice
59 63 read_attribute(:request) || {}
60 64 end
61 65  
  66 + def url
  67 + request['url']
  68 + end
  69 +
  70 + def host
  71 + uri = url && URI.parse(url)
  72 + uri.blank? ? "N/A" : uri.host
  73 + rescue URI::InvalidURIError
  74 + "N/A"
  75 + end
  76 +
62 77 def env_vars
63 78 request['cgi-data'] || {}
64 79 end
... ... @@ -94,11 +109,14 @@ class Notice
94 109 problem.inc(:notices_count, -1) if err
95 110 end
96 111  
  112 + def remove_cached_attributes_from_problem
  113 + problem.remove_cached_notice_attribures(self)
  114 + end
  115 +
97 116 def unresolve_problem
98 117 problem.update_attribute(:resolved, false) if problem.resolved?
99 118 end
100 119  
101   -
102 120 def cache_attributes_on_problem
103 121 problem.cache_notice_attributes(self)
104 122 end
... ...
app/models/problem.rb
... ... @@ -18,6 +18,9 @@ class Problem
18 18 field :environment
19 19 field :klass
20 20 field :where
  21 + field :user_agents, :type => Array, :default => []
  22 + field :messages, :type => Array, :default => []
  23 + field :hosts, :type => Array, :default => []
21 24  
22 25 index :app_id
23 26 index :app_name
... ... @@ -123,8 +126,20 @@ class Problem
123 126 :message => notice.message,
124 127 :environment => notice.environment_name,
125 128 :klass => notice.klass,
126   - :where => notice.where) if notice
  129 + :where => notice.where,
  130 + :messages => messages.push(notice.message),
  131 + :hosts => hosts.push(notice.host),
  132 + :user_agents => user_agents.push(notice.user_agent_string)
  133 + ) if notice
127 134 update_attributes!(attrs)
128 135 end
  136 +
  137 + def remove_cached_notice_attribures(notice)
  138 + messages.delete_at(messages.index(notice.message))
  139 + hosts.delete_at(hosts.index(notice.host))
  140 + user_agents.delete_at(user_agents.index(notice.user_agent_string))
  141 + save!
  142 + end
  143 +
129 144 end
130 145  
... ...
app/views/errs/show.html.haml
... ... @@ -56,7 +56,7 @@
56 56 - if @notice
57 57 #summary
58 58 %h3 Summary
59   - = render 'notices/summary', :notice => @notice
  59 + = render 'notices/summary', :notice => @notice, :problem => @problem
60 60  
61 61 #backtrace
62 62 %h3 Backtrace
... ...
app/views/notices/_summary.html.haml
... ... @@ -2,7 +2,7 @@
2 2 %table.summary
3 3 %tr
4 4 %th Message
5   - %td.main.nowrap= message_graph(notice.problem)
  5 + %td.main.nowrap= message_graph(problem)
6 6 - if notice.request['url'].present?
7 7 %tr
8 8 %th URL
... ... @@ -15,13 +15,13 @@
15 15 %td= notice.created_at.to_s(:micro)
16 16 %tr
17 17 %th Similar
18   - %td= notice.problem.notices.count - 1
  18 + %td= problem.notices_count - 1
19 19 %tr
20 20 %th Browser
21   - %td= user_agent_graph(notice.problem)
  21 + %td= user_agent_graph(problem)
22 22 %tr
23 23 %th Tenant
24   - %td= tenant_graph(notice.problem)
  24 + %td= tenant_graph(problem)
25 25 %tr
26 26 %th App Server
27 27 %td= notice.server_environment && notice.server_environment["hostname"]
... ...
db/migrate/20111019064503_cache_problem_statistics.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +class CacheProblemStatistics < Mongoid::Migration
  2 + def self.up
  3 + Problem.all.each do |problem|
  4 + problem.notices.each do |notice|
  5 + problem.messages << notice.message
  6 + problem.hosts << notice.host
  7 + problem.user_agents << notice.user_agent_string
  8 + end
  9 + problem.save!
  10 + end
  11 + end
  12 +
  13 + def self.down
  14 + Problem.all.each do |problem|
  15 + problem.update_attributes(:messages => [], :hosts => [], :user_agents => [])
  16 + end
  17 + end
  18 +end
0 19 \ No newline at end of file
... ...
spec/helpers/application_helper_spec.rb
... ... @@ -10,18 +10,3 @@ require &#39;spec_helper&#39;
10 10 # end
11 11 # end
12 12 # end
13   -describe ApplicationHelper do
14   - describe "get_host" do
15   - it "returns host if url is valid" do
16   - helper.get_host("http://example.com/resource/12").should == 'example.com'
17   - end
18   -
19   - it "returns 'N/A' when url is not valid" do
20   - helper.get_host("some string").should == 'N/A'
21   - end
22   -
23   - it "returns 'N/A' when url is empty" do
24   - helper.get_host({}).should == 'N/A'
25   - end
26   - end
27   -end
... ...
spec/models/notice_spec.rb
... ... @@ -82,6 +82,36 @@ describe Notice do
82 82 end
83 83 end
84 84  
  85 + describe "user agent string" do
  86 + it "should be parsed and human-readable" do
  87 + notice = Factory.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}})
  88 + notice.user_agent_string.should == 'Chrome 10.0.648.204'
  89 + end
  90 +
  91 + it "should be nil if HTTP_USER_AGENT is blank" do
  92 + notice = Factory.build(:notice)
  93 + notice.user_agent_string.should == "N/A"
  94 + end
  95 + end
  96 +
  97 + describe "host" do
  98 + it "returns host if url is valid" do
  99 + notice = Factory.build(:notice, :request => {'url' => "http://example.com/resource/12"})
  100 + notice.host.should == 'example.com'
  101 + end
  102 +
  103 + it "returns 'N/A' when url is not valid" do
  104 + notice = Factory.build(:notice, :request => {'url' => "some string"})
  105 + notice.host.should == 'N/A'
  106 + end
  107 +
  108 + it "returns 'N/A' when url is empty" do
  109 + notice = Factory.build(:notice, :request => {})
  110 + notice.host.should == 'N/A'
  111 + end
  112 +
  113 + end
  114 +
85 115  
86 116 describe "email notifications (configured individually for each app)" do
87 117 custom_thresholds = [2, 4, 8, 16, 32, 64]
... ...
spec/models/problem_spec.rb
... ... @@ -197,5 +197,84 @@ describe Problem do
197 197 }.should change(problem, :last_deploy_at).from(@last_deploy).to(next_deploy)
198 198 end
199 199 end
  200 +
  201 + context "notice messages cache" do
  202 + before do
  203 + @app = Factory(:app)
  204 + @problem = Factory(:problem, :app => @app)
  205 + @err = Factory(:err, :problem => @problem)
  206 + end
  207 +
  208 + it "#messages returns [] by default" do
  209 + @problem.messages.should == []
  210 + end
  211 +
  212 + it "adding a notice adds a string to #messages" do
  213 + lambda {
  214 + Factory(:notice, :err => @err, :message => 'ERR 1')
  215 + }.should change(@problem, :messages).from([]).to(['ERR 1'])
  216 + end
  217 +
  218 + it "removing a notice removes string from #messages" do
  219 + notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')
  220 + lambda {
  221 + @err.notices.first.destroy
  222 + @problem.reload
  223 + }.should change(@problem, :messages).from(['ERR 1']).to([])
  224 + end
  225 + end
  226 +
  227 + context "notice hosts cache" do
  228 + before do
  229 + @app = Factory(:app)
  230 + @problem = Factory(:problem, :app => @app)
  231 + @err = Factory(:err, :problem => @problem)
  232 + end
  233 +
  234 + it "#hosts returns [] by default" do
  235 + @problem.hosts.should == []
  236 + end
  237 +
  238 + it "adding a notice adds a string to #hosts" do
  239 + lambda {
  240 + Factory(:notice, :err => @err, :request => {'url' => "http://example.com/resource/12"})
  241 + }.should change(@problem, :hosts).from([]).to(['example.com'])
  242 + end
  243 +
  244 + it "removing a notice removes string from #hosts" do
  245 + notice1 = Factory(:notice, :err => @err, :request => {'url' => "http://example.com/resource/12"})
  246 + lambda {
  247 + @err.notices.first.destroy
  248 + @problem.reload
  249 + }.should change(@problem, :hosts).from(['example.com']).to([])
  250 + end
  251 + end
  252 +
  253 + context "notice user_agents cache" do
  254 + before do
  255 + @app = Factory(:app)
  256 + @problem = Factory(:problem, :app => @app)
  257 + @err = Factory(:err, :problem => @problem)
  258 + end
  259 +
  260 + it "#user_agents returns [] by default" do
  261 + @problem.user_agents.should == []
  262 + end
  263 +
  264 + it "adding a notice adds a string to #user_agents" do
  265 + lambda {
  266 + Factory(:notice, :err => @err, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}})
  267 + }.should change(@problem, :user_agents).from([]).to(['Chrome 10.0.648.204'])
  268 + end
  269 +
  270 + it "removing a notice removes string from #user_agents" do
  271 + notice1 = Factory(:notice, :err => @err, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}})
  272 + lambda {
  273 + @err.notices.first.destroy
  274 + @problem.reload
  275 + }.should change(@problem, :user_agents).from(['Chrome 10.0.648.204']).to([])
  276 + end
  277 + end
  278 +
200 279 end
201 280  
... ...