rorvswild 0.6.1 → 1.0.0.pre.alpha
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.
- checksums.yaml +4 -4
- data/Gemfile +8 -0
- data/README.md +70 -1
- data/lib/rorvswild.rb +15 -17
- data/lib/rorvswild/agent.rb +180 -0
- data/lib/rorvswild/client.rb +9 -285
- data/lib/rorvswild/installer.rb +0 -1
- data/lib/rorvswild/location.rb +10 -19
- data/lib/rorvswild/plugin/action_controller.rb +46 -0
- data/lib/rorvswild/plugin/action_mailer.rb +23 -0
- data/lib/rorvswild/plugin/action_view.rb +26 -0
- data/lib/rorvswild/plugin/active_job.rb +16 -0
- data/lib/rorvswild/plugin/active_record.rb +31 -0
- data/lib/rorvswild/plugin/delayed_job.rb +16 -0
- data/lib/rorvswild/plugin/mongo.rb +5 -4
- data/lib/rorvswild/plugin/net_http.rb +12 -2
- data/lib/rorvswild/plugin/redis.rb +1 -1
- data/lib/rorvswild/plugins.rb +14 -0
- data/lib/rorvswild/rails_loader.rb +1 -1
- data/lib/rorvswild/section.rb +54 -0
- data/lib/rorvswild/version.rb +1 -1
- data/test/helper.rb +13 -0
- data/test/measure_nested_sections_test.rb +38 -0
- data/test/plugin/action_controller_test.rb +58 -0
- data/test/plugin/action_mailer_test.rb +27 -0
- data/test/plugin/action_view_test.rb +40 -0
- data/test/plugin/active_job_test.rb +21 -0
- data/test/plugin/active_record_test.rb +39 -0
- data/test/plugin/delayed_job_test.rb +35 -0
- data/test/plugin/mongo_test.rb +9 -20
- data/test/plugin/net_http_test.rb +12 -23
- data/test/plugin/redis_test.rb +15 -27
- data/test/rorvswild_test.rb +141 -0
- data/test/run.rb +1 -1
- data/test/section_test.rb +53 -0
- metadata +32 -7
- data/test/ror_vs_wild_test.rb +0 -147
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1736359534ad385b3f94023b6f58aedeb80758d9
|
4
|
+
data.tar.gz: 7b3977b85d8cc46cf332bccd8ada6c8ed482a1a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0851e785429d0263cdf0f0b98792db188a9616fc16bb98847dd2e1ccb979e3627a5a591f6282468c62e18ca46ccba79aaa10441c60f2b258d51b174d28c25489
|
7
|
+
data.tar.gz: 02e3b948d90d5fb378c6707d6e2d563348a1549bec9465e7a5dd2d00488d918d229e9d5ef0ef11dc3cf4453ea8d686539b8ff6c1e6695ae5ef768ab1e8b021b3
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,9 +16,78 @@ For those who prefer to manually use an initializer, they can do the following.
|
|
16
16
|
|
17
17
|
```ruby
|
18
18
|
# config/initializers/rorvswild.rb
|
19
|
-
RorVsWild
|
19
|
+
RorVsWild.start(api_key: API_KEY)
|
20
20
|
```
|
21
21
|
|
22
|
+
## Measure any code
|
23
|
+
|
24
|
+
You can measure any code like this (useful to monitor cronjobs):
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
RorVsWild.measure_code("User.all.do_something_great")
|
28
|
+
```
|
29
|
+
|
30
|
+
Or like that:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
RorVsWild.measure_block("A great job name") { User.all.do_something_great }
|
34
|
+
```
|
35
|
+
|
36
|
+
Then it will appears in the jobs page.
|
37
|
+
|
38
|
+
Note that Calling `measure_code` or `measure_block` inside or a request or a job will add a section.
|
39
|
+
That is convenient to profile finely parts of your code.
|
40
|
+
|
41
|
+
## Send errors manually
|
42
|
+
|
43
|
+
When you already have a begin / rescue block, this manner suits well:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
begin
|
47
|
+
# Your code ...
|
48
|
+
rescue => exception
|
49
|
+
RorVsWild.record_error(exception)
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
If you prefer to be concise, just run the code from a block:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
RorVsWild.catch_error { 1 / 0 } # => #<ZeroDivisionError: divided by 0>
|
57
|
+
```
|
58
|
+
|
59
|
+
Moreover, you can provide extra details when capturing errors:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
RorVsWild.record_error(exception, {something: "important"})
|
63
|
+
```
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
RorVsWild.catch_error(something: "important") { 1 / 0 }
|
67
|
+
```
|
68
|
+
|
69
|
+
## Ignore exceptions
|
70
|
+
|
71
|
+
By using the ignored_exceptions parameter you can prevent RorVsWild from recording specific exceptions.
|
72
|
+
|
73
|
+
```yaml
|
74
|
+
# config/rorvswild.yml
|
75
|
+
production:
|
76
|
+
api_key: API_KEY
|
77
|
+
ignored_exceptions:
|
78
|
+
- ActionController::RoutingError
|
79
|
+
- ZeroDivisionError
|
80
|
+
```
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# config/initializers/rorvswild.rb
|
84
|
+
RorVsWild::Client.new(
|
85
|
+
api_key: "API_KEY",
|
86
|
+
ignored_exceptions: ["ActionController::RoutingError", "ZeroDivisionError"])
|
87
|
+
```
|
88
|
+
|
89
|
+
By default ActionController::RoutingError is ignored in order to not be flooded with 404.
|
90
|
+
|
22
91
|
## Contributing
|
23
92
|
|
24
93
|
1. Fork it ( https://github.com/[my-github-username]/rorvswild/fork )
|
data/lib/rorvswild.rb
CHANGED
@@ -1,35 +1,33 @@
|
|
1
1
|
require "rorvswild/version"
|
2
2
|
require "rorvswild/location"
|
3
|
-
require "rorvswild/
|
4
|
-
require "rorvswild/plugin/mongo"
|
5
|
-
require "rorvswild/plugin/resque"
|
6
|
-
require "rorvswild/plugin/sidekiq"
|
7
|
-
require "rorvswild/plugin/net_http"
|
3
|
+
require "rorvswild/section"
|
8
4
|
require "rorvswild/client"
|
5
|
+
require "rorvswild/plugins"
|
6
|
+
require "rorvswild/agent"
|
9
7
|
|
10
8
|
module RorVsWild
|
11
|
-
def self.
|
12
|
-
|
9
|
+
def self.start(config)
|
10
|
+
@agent = Agent.new(config)
|
13
11
|
end
|
14
12
|
|
15
|
-
def self.
|
16
|
-
|
13
|
+
def self.agent
|
14
|
+
@agent
|
17
15
|
end
|
18
16
|
|
19
|
-
def self.
|
20
|
-
|
17
|
+
def self.measure_code(code)
|
18
|
+
agent ? agent.measure_code(code) : eval(code)
|
21
19
|
end
|
22
20
|
|
23
|
-
def self.
|
24
|
-
|
21
|
+
def self.measure_block(name, &block)
|
22
|
+
agent ? agent.measure_block(name , &block) : block.call
|
25
23
|
end
|
26
24
|
|
27
|
-
def self.
|
28
|
-
|
25
|
+
def self.catch_error(extra_details = nil, &block)
|
26
|
+
agent ? agent.catch_error(extra_details, &block) : block.call
|
29
27
|
end
|
30
28
|
|
31
|
-
def self.
|
32
|
-
|
29
|
+
def self.record_error(exception, extra_details = nil)
|
30
|
+
agent.record_error(exception, extra_details) if agent
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module RorVsWild
|
4
|
+
class Agent
|
5
|
+
include RorVsWild::Location
|
6
|
+
|
7
|
+
def self.default_config
|
8
|
+
{
|
9
|
+
api_url: "https://www.rorvswild.com/api",
|
10
|
+
ignored_exceptions: [],
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :api_url, :api_key, :app_id, :app_root, :ignored_exceptions
|
15
|
+
|
16
|
+
attr_reader :app_root_regex, :client
|
17
|
+
|
18
|
+
def initialize(config)
|
19
|
+
config = self.class.default_config.merge(config)
|
20
|
+
@ignored_exceptions = config[:ignored_exceptions]
|
21
|
+
@app_root = config[:app_root]
|
22
|
+
@logger = config[:logger]
|
23
|
+
@data = {}
|
24
|
+
@client = Client.new(config)
|
25
|
+
|
26
|
+
if defined?(Rails)
|
27
|
+
@logger ||= Rails.logger
|
28
|
+
@app_root ||= Rails.root.to_s
|
29
|
+
config = Rails.application.config
|
30
|
+
@ignored_exceptions ||= %w[ActionController::RoutingError] + config.action_dispatch.rescue_responses.map { |(key,value)| key }
|
31
|
+
end
|
32
|
+
|
33
|
+
@logger ||= Logger.new(STDERR)
|
34
|
+
@app_root_regex = app_root ? /\A#{app_root}/ : nil
|
35
|
+
|
36
|
+
setup_plugins
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_plugins
|
40
|
+
Plugin::NetHttp.setup
|
41
|
+
|
42
|
+
Plugin::Redis.setup
|
43
|
+
Plugin::Mongo.setup
|
44
|
+
|
45
|
+
Plugin::Resque.setup
|
46
|
+
Plugin::Sidekiq.setup
|
47
|
+
Plugin::ActiveJob.setup
|
48
|
+
Plugin::DelayedJob.setup
|
49
|
+
|
50
|
+
Plugin::ActionView.setup
|
51
|
+
Plugin::ActiveRecord.setup
|
52
|
+
Plugin::ActionMailer.setup
|
53
|
+
Plugin::ActionController.setup
|
54
|
+
end
|
55
|
+
|
56
|
+
def measure_code(code)
|
57
|
+
measure_block(code) { eval(code) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def measure_block(name, kind = "code", &block)
|
61
|
+
data[:name] ? measure_section(name, kind, &block) : measure_job(name, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def measure_section(name, kind = "code", &block)
|
65
|
+
return block.call unless data[:name]
|
66
|
+
begin
|
67
|
+
RorVsWild::Section.start do |section|
|
68
|
+
section.command = name
|
69
|
+
section.kind = kind
|
70
|
+
end
|
71
|
+
block.call
|
72
|
+
ensure
|
73
|
+
RorVsWild::Section.stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def measure_job(name, &block)
|
78
|
+
return block.call if data[:name] # Prevent from recursive jobs
|
79
|
+
initialize_data(name)
|
80
|
+
begin
|
81
|
+
block.call
|
82
|
+
rescue Exception => ex
|
83
|
+
data[:error] = exception_to_hash(ex) if !ignored_exception?(ex)
|
84
|
+
raise
|
85
|
+
ensure
|
86
|
+
data[:runtime] = (Time.now - data[:started_at]) * 1000
|
87
|
+
post_job
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def start_request(payload)
|
92
|
+
return if data[:name]
|
93
|
+
initialize_data(payload[:name])
|
94
|
+
data[:path] = payload[:path]
|
95
|
+
end
|
96
|
+
|
97
|
+
def stop_request
|
98
|
+
return unless data[:name]
|
99
|
+
data[:runtime] = (Time.now.utc - data[:started_at]) * 1000
|
100
|
+
post_request
|
101
|
+
end
|
102
|
+
|
103
|
+
def catch_error(extra_details = nil, &block)
|
104
|
+
begin
|
105
|
+
block.call
|
106
|
+
rescue Exception => ex
|
107
|
+
record_error(ex, extra_details) if !ignored_exception?(ex)
|
108
|
+
ex
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def record_error(exception, extra_details = nil)
|
113
|
+
post_error(exception_to_hash(exception, extra_details))
|
114
|
+
end
|
115
|
+
|
116
|
+
def push_exception(exception)
|
117
|
+
return if ignored_exception?(exception)
|
118
|
+
data[:error] = exception_to_hash(exception)
|
119
|
+
end
|
120
|
+
|
121
|
+
def data
|
122
|
+
@data[Thread.current.object_id] ||= {}
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_section(section)
|
126
|
+
return unless data[:sections]
|
127
|
+
if sibling = data[:sections].find { |s| s.sibling?(section) }
|
128
|
+
sibling.merge(section)
|
129
|
+
else
|
130
|
+
data[:sections] << section
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#######################
|
135
|
+
### Private methods ###
|
136
|
+
#######################
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def initialize_data(name)
|
141
|
+
data[:name] = name
|
142
|
+
data[:sections] = []
|
143
|
+
data[:section_stack] = []
|
144
|
+
data[:started_at] = Time.now.utc
|
145
|
+
end
|
146
|
+
|
147
|
+
def cleanup_data
|
148
|
+
@data.delete(Thread.current.object_id)
|
149
|
+
end
|
150
|
+
|
151
|
+
def post_request
|
152
|
+
client.post_async("/requests".freeze, request: cleanup_data)
|
153
|
+
end
|
154
|
+
|
155
|
+
def post_job
|
156
|
+
client.post_async("/jobs".freeze, job: cleanup_data)
|
157
|
+
end
|
158
|
+
|
159
|
+
def post_error(hash)
|
160
|
+
post_async("/errors".freeze, error: hash)
|
161
|
+
end
|
162
|
+
|
163
|
+
def exception_to_hash(exception, extra_details = nil)
|
164
|
+
file, line, method = extract_most_relevant_file_and_line(exception.backtrace_locations)
|
165
|
+
{
|
166
|
+
method: method,
|
167
|
+
line: line.to_i,
|
168
|
+
file: relative_path(file),
|
169
|
+
message: exception.message,
|
170
|
+
backtrace: exception.backtrace,
|
171
|
+
exception: exception.class.to_s,
|
172
|
+
extra_details: extra_details,
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def ignored_exception?(exception)
|
177
|
+
ignored_exceptions.include?(exception.class.to_s)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/rorvswild/client.rb
CHANGED
@@ -1,281 +1,22 @@
|
|
1
|
-
require "json/ext"
|
2
|
-
require "net/http"
|
3
|
-
require "logger"
|
4
|
-
require "uri"
|
5
1
|
require "set"
|
2
|
+
require "uri"
|
3
|
+
require "net/http"
|
4
|
+
require "json/ext"
|
6
5
|
|
7
6
|
module RorVsWild
|
8
7
|
class Client
|
9
|
-
|
10
|
-
|
11
|
-
def self.default_config
|
12
|
-
{
|
13
|
-
api_url: "https://www.rorvswild.com/api",
|
14
|
-
explain_sql_threshold: 500,
|
15
|
-
ignored_exceptions: [],
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_reader :api_url, :api_key, :app_id, :explain_sql_threshold, :app_root, :ignored_exceptions
|
8
|
+
HTTPS = "https".freeze
|
9
|
+
CERTIFICATE_AUTHORITIES_PATH = File.expand_path("../../../cacert.pem", __FILE__)
|
20
10
|
|
21
|
-
attr_reader :
|
11
|
+
attr_reader :api_url, :api_key, :threads
|
22
12
|
|
23
13
|
def initialize(config)
|
24
|
-
|
25
|
-
@explain_sql_threshold = config[:explain_sql_threshold]
|
26
|
-
@ignored_exceptions = config[:ignored_exceptions]
|
27
|
-
@app_root = config[:app_root]
|
14
|
+
Kernel.at_exit(&method(:at_exit))
|
28
15
|
@api_url = config[:api_url]
|
29
16
|
@api_key = config[:api_key]
|
30
|
-
@app_id = config[:app_id]
|
31
|
-
@logger = config[:logger]
|
32
17
|
@threads = Set.new
|
33
|
-
@data = {}
|
34
|
-
|
35
|
-
if defined?(Rails)
|
36
|
-
@logger ||= Rails.logger
|
37
|
-
@app_root ||= Rails.root.to_s
|
38
|
-
config = Rails.application.config
|
39
|
-
@parameter_filter = ActionDispatch::Http::ParameterFilter.new(config.filter_parameters)
|
40
|
-
@ignored_exceptions ||= %w[ActionController::RoutingError] + config.action_dispatch.rescue_responses.map { |(key,value)| key }
|
41
|
-
end
|
42
|
-
|
43
|
-
@logger ||= Logger.new(STDERR)
|
44
|
-
@app_root_regex = app_root ? /\A#{app_root}/ : nil
|
45
|
-
|
46
|
-
setup_callbacks
|
47
|
-
RorVsWild.register_client(self)
|
48
|
-
end
|
49
|
-
|
50
|
-
def setup_callbacks
|
51
|
-
client = self
|
52
|
-
if defined?(ActiveSupport::Notifications)
|
53
|
-
ActiveSupport::Notifications.subscribe("sql.active_record", &method(:after_sql_query))
|
54
|
-
ActiveSupport::Notifications.subscribe("render_partial.action_view", &method(:after_view_rendering))
|
55
|
-
ActiveSupport::Notifications.subscribe("render_template.action_view", &method(:after_view_rendering))
|
56
|
-
ActiveSupport::Notifications.subscribe("process_action.action_controller", &method(:after_http_request))
|
57
|
-
ActiveSupport::Notifications.subscribe("start_processing.action_controller", &method(:before_http_request))
|
58
|
-
ActionController::Base.rescue_from(StandardError) { |exception| client.after_exception(exception, self) }
|
59
|
-
end
|
60
|
-
|
61
|
-
Plugin::Redis.setup
|
62
|
-
Plugin::Mongo.setup
|
63
|
-
Plugin::Resque.setup
|
64
|
-
Plugin::Sidekiq.setup
|
65
|
-
Plugin::NetHttp.setup
|
66
|
-
Kernel.at_exit(&method(:at_exit))
|
67
|
-
ActiveJob::Base.around_perform(&method(:around_active_job)) if defined?(ActiveJob::Base)
|
68
|
-
Delayed::Worker.lifecycle.around(:invoke_job, &method(:around_delayed_job)) if defined?(Delayed::Worker)
|
69
|
-
end
|
70
|
-
|
71
|
-
def before_http_request(name, start, finish, id, payload)
|
72
|
-
request.merge!(controller: payload[:controller], action: payload[:action], path: payload[:path], queries: [], views: {})
|
73
|
-
end
|
74
|
-
|
75
|
-
def after_http_request(name, start, finish, id, payload)
|
76
|
-
request[:db_runtime] = (payload[:db_runtime] || 0).round
|
77
|
-
request[:view_runtime] = (payload[:view_runtime] || 0).round
|
78
|
-
request[:other_runtime] = compute_duration(start, finish) - request[:db_runtime] - request[:view_runtime]
|
79
|
-
request[:error][:parameters] = filter_sensitive_data(payload[:params]) if request[:error]
|
80
|
-
post_request
|
81
|
-
rescue => exception
|
82
|
-
log_error(exception)
|
83
|
-
end
|
84
|
-
|
85
|
-
IGNORED_QUERIES = %w[EXPLAIN SCHEMA].freeze
|
86
|
-
|
87
|
-
def after_sql_query(name, start, finish, id, payload)
|
88
|
-
return if !queries || IGNORED_QUERIES.include?(payload[:name])
|
89
|
-
file, line, method = extract_most_relevant_location(caller)
|
90
|
-
runtime, sql = compute_duration(start, finish), payload[:sql]
|
91
|
-
plan = runtime >= explain_sql_threshold ? explain(payload[:sql], payload[:binds]) : nil
|
92
|
-
push_query(kind: "sql", file: file, line: line, method: method, command: sql, plan: plan, runtime: runtime)
|
93
|
-
rescue => exception
|
94
|
-
log_error(exception)
|
95
|
-
end
|
96
|
-
|
97
|
-
def after_view_rendering(name, start, finish, id, payload)
|
98
|
-
if views
|
99
|
-
if view = views[file = relative_path(payload[:identifier])]
|
100
|
-
view[:runtime] += compute_duration(start, finish)
|
101
|
-
view[:times] += 1
|
102
|
-
else
|
103
|
-
views[file] = {file: file, runtime: compute_duration(start, finish), times: 1}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def after_exception(exception, controller)
|
109
|
-
if !ignored_exception?(exception)
|
110
|
-
file, line = exception.backtrace.first.split(":")
|
111
|
-
request[:error] = exception_to_hash(exception).merge(
|
112
|
-
session: controller.session.to_hash,
|
113
|
-
environment_variables: filter_sensitive_data(filter_environment_variables(controller.request.env))
|
114
|
-
)
|
115
|
-
end
|
116
|
-
raise exception
|
117
|
-
end
|
118
|
-
|
119
|
-
def around_active_job(job, block)
|
120
|
-
measure_block(job.class.name, &block)
|
121
|
-
end
|
122
|
-
|
123
|
-
def around_delayed_job(job, &block)
|
124
|
-
measure_block(job.name) { block.call(job) }
|
125
|
-
end
|
126
|
-
|
127
|
-
def measure_code(code)
|
128
|
-
measure_block(code) { eval(code) }
|
129
|
-
end
|
130
|
-
|
131
|
-
def measure_block(name, &block)
|
132
|
-
return block.call if job[:name] # Prevent from recursive jobs
|
133
|
-
job[:name] = name
|
134
|
-
job[:queries] = []
|
135
|
-
started_at = Time.now
|
136
|
-
cpu_time_offset = cpu_time
|
137
|
-
begin
|
138
|
-
block.call
|
139
|
-
rescue Exception => ex
|
140
|
-
job[:error] = exception_to_hash(ex) if !ignored_exception?(ex)
|
141
|
-
raise
|
142
|
-
ensure
|
143
|
-
job[:runtime] = (Time.now - started_at) * 1000
|
144
|
-
job[:cpu_runtime] = (cpu_time - cpu_time_offset) * 1000
|
145
|
-
post_job
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def measure_query(kind, command, &block)
|
150
|
-
return block.call if @query_started_at # Prevent from recursive queries
|
151
|
-
@query_started_at = Time.now.utc
|
152
|
-
begin
|
153
|
-
result = block.call
|
154
|
-
runtime = (Time.now.utc - @query_started_at) * 1000
|
155
|
-
file, line, method = extract_most_relevant_location(caller)
|
156
|
-
push_query(kind: kind, command: command, file: file, line: line, method: method, runtime: runtime)
|
157
|
-
result
|
158
|
-
ensure
|
159
|
-
@query_started_at = nil
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def catch_error(extra_details = nil, &block)
|
164
|
-
begin
|
165
|
-
block.call
|
166
|
-
rescue Exception => ex
|
167
|
-
record_error(ex, extra_details) if !ignored_exception?(ex)
|
168
|
-
ex
|
169
|
-
end
|
170
18
|
end
|
171
19
|
|
172
|
-
def record_error(exception, extra_details = nil)
|
173
|
-
post_error(exception_to_hash(exception, extra_details))
|
174
|
-
end
|
175
|
-
|
176
|
-
def cpu_time
|
177
|
-
time = Process.times
|
178
|
-
time.utime + time.stime + time.cutime + time.cstime
|
179
|
-
end
|
180
|
-
|
181
|
-
#######################
|
182
|
-
### Private methods ###
|
183
|
-
#######################
|
184
|
-
|
185
|
-
private
|
186
|
-
|
187
|
-
def queries
|
188
|
-
data[:queries]
|
189
|
-
end
|
190
|
-
|
191
|
-
def views
|
192
|
-
data[:views]
|
193
|
-
end
|
194
|
-
|
195
|
-
def job
|
196
|
-
data
|
197
|
-
end
|
198
|
-
|
199
|
-
def request
|
200
|
-
data
|
201
|
-
end
|
202
|
-
|
203
|
-
def data
|
204
|
-
@data[Thread.current.object_id] ||= {}
|
205
|
-
end
|
206
|
-
|
207
|
-
def cleanup_data
|
208
|
-
@data.delete(Thread.current.object_id)
|
209
|
-
end
|
210
|
-
|
211
|
-
MEANINGLESS_QUERIES = %w[BEGIN COMMIT].freeze
|
212
|
-
|
213
|
-
def push_query(query)
|
214
|
-
return if !queries
|
215
|
-
hash = queries.find { |hash| hash[:line] == query[:line] && hash[:file] == query[:file] && hash[:kind] == query[:kind] }
|
216
|
-
queries << hash = {kind: query[:kind], file: query[:file], line: query[:line], runtime: 0, times: 0} if !hash
|
217
|
-
hash[:runtime] += query[:runtime]
|
218
|
-
if !MEANINGLESS_QUERIES.include?(query[:command])
|
219
|
-
hash[:times] += 1
|
220
|
-
hash[:command] ||= query[:command]
|
221
|
-
hash[:plan] ||= query[:plan] if query[:plan]
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def slowest_views
|
226
|
-
views.values.sort { |h1, h2| h2[:runtime] <=> h1[:runtime] }[0, 25]
|
227
|
-
end
|
228
|
-
|
229
|
-
def slowest_queries
|
230
|
-
queries.sort { |h1, h2| h2[:runtime] <=> h1[:runtime] }[0, 25]
|
231
|
-
end
|
232
|
-
|
233
|
-
SELECT_REGEX = /\Aselect/i.freeze
|
234
|
-
|
235
|
-
def explain(sql, binds)
|
236
|
-
ActiveRecord::Base.connection.explain(sql, binds) if sql =~ SELECT_REGEX
|
237
|
-
end
|
238
|
-
|
239
|
-
def post_request
|
240
|
-
attributes = request.merge(queries: slowest_queries, views: slowest_views)
|
241
|
-
post_async("/requests".freeze, request: attributes)
|
242
|
-
ensure
|
243
|
-
cleanup_data
|
244
|
-
end
|
245
|
-
|
246
|
-
def post_job
|
247
|
-
attributes = job.merge(queries: slowest_queries)
|
248
|
-
post_async("/jobs".freeze, job: attributes)
|
249
|
-
rescue => exception
|
250
|
-
log_error(exception)
|
251
|
-
ensure
|
252
|
-
cleanup_data
|
253
|
-
end
|
254
|
-
|
255
|
-
def post_error(hash)
|
256
|
-
post_async("/errors".freeze, error: hash)
|
257
|
-
end
|
258
|
-
|
259
|
-
def compute_duration(start, finish)
|
260
|
-
((finish - start) * 1000)
|
261
|
-
end
|
262
|
-
|
263
|
-
def exception_to_hash(exception, extra_details = nil)
|
264
|
-
file, line, method = extract_most_relevant_location(exception.backtrace)
|
265
|
-
{
|
266
|
-
method: method,
|
267
|
-
line: line.to_i,
|
268
|
-
file: relative_path(file),
|
269
|
-
message: exception.message,
|
270
|
-
backtrace: exception.backtrace,
|
271
|
-
exception: exception.class.to_s,
|
272
|
-
extra_details: extra_details,
|
273
|
-
}
|
274
|
-
end
|
275
|
-
|
276
|
-
HTTPS = "https".freeze
|
277
|
-
CERTIFICATE_AUTHORITIES_PATH = File.expand_path("../../../cacert.pem", __FILE__)
|
278
|
-
|
279
20
|
def post(path, data)
|
280
21
|
uri = URI(api_url + path)
|
281
22
|
http = Net::HTTP.new(uri.host, uri.port)
|
@@ -286,9 +27,9 @@ module RorVsWild
|
|
286
27
|
http.use_ssl = true
|
287
28
|
end
|
288
29
|
|
289
|
-
post = Net::HTTP::Post.new(uri.path)
|
30
|
+
post = Net::HTTP::Post.new(uri.path, "X-Gem-Version".freeze => RorVsWild::VERSION)
|
290
31
|
post.content_type = "application/json".freeze
|
291
|
-
post.basic_auth(
|
32
|
+
post.basic_auth(nil, api_key)
|
292
33
|
post.body = data.to_json
|
293
34
|
http.request(post)
|
294
35
|
end
|
@@ -307,22 +48,5 @@ module RorVsWild
|
|
307
48
|
def at_exit
|
308
49
|
threads.each(&:join)
|
309
50
|
end
|
310
|
-
|
311
|
-
def filter_sensitive_data(hash)
|
312
|
-
@parameter_filter ? @parameter_filter.filter(hash) : hash
|
313
|
-
end
|
314
|
-
|
315
|
-
def filter_environment_variables(hash)
|
316
|
-
hash.clone.keep_if { |key,value| key == key.upcase }
|
317
|
-
end
|
318
|
-
|
319
|
-
def ignored_exception?(exception)
|
320
|
-
ignored_exceptions.include?(exception.class.to_s)
|
321
|
-
end
|
322
|
-
|
323
|
-
def log_error(exception)
|
324
|
-
@logger.error("[RorVsWild] " + exception.inspect)
|
325
|
-
@logger.error("[RorVsWild] " + exception.backtrace.join("\n[RorVsWild] "))
|
326
|
-
end
|
327
51
|
end
|
328
52
|
end
|