Skip to content

Commit ccb88bb

Browse files
committed
WIP: Improve capture performance
Currently 2-3x faster. TODO: Any frozen objects that may be bad? Should we provide some sort of fallback bath here? Reduce allocations in RemoveCircularReferences, if possible
1 parent 823c905 commit ccb88bb

14 files changed

+187
-82
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
coverage
22
pkg
33
ruby/
4+
log/
45
docs/_build
56
.bundle
67
*.gem

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ group :development do
1212
gem "pry-coolline"
1313
gem "benchmark-ips"
1414
gem "benchmark-ipsa"
15-
gem "yajl-ruby", :platforms => :mri
15+
gem "ruby-prof"
1616
end

benchmarks/allocation_report.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require 'benchmark/ips'
2+
require 'benchmark/ipsa'
3+
require 'raven'
4+
require 'raven/breadcrumbs/logger'
5+
require 'raven/transports/dummy'
6+
require_relative "../spec/support/test_rails_app/app"
7+
8+
TestApp.configure do |config|
9+
config.middleware.delete ActionDispatch::DebugExceptions
10+
config.middleware.delete ActionDispatch::ShowExceptions
11+
end
12+
13+
Raven.configure do |config|
14+
config.logger = Logger.new(nil)
15+
config.dsn = "dummy://12345:[email protected]:3000/sentry/42"
16+
end
17+
18+
TestApp.initialize!
19+
@app = Rack::MockRequest.new(TestApp)
20+
RAILS_EXC = begin
21+
@app.get("/exception")
22+
rescue => exc
23+
exc
24+
end
25+
26+
report = MemoryProfiler.report do
27+
Raven.capture_exception(RAILS_EXC)
28+
end
29+
30+
report.pretty_print

benchmarks/async_benchmark.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'benchmark/ips'
2+
require 'benchmark/ipsa'
3+
require 'raven'
4+
require 'raven/breadcrumbs/logger'
5+
require 'raven/transports/dummy'
6+
require 'rack/test'
7+
require_relative "../spec/support/test_rails_app/app"
8+
9+
def build_exception
10+
1 / 0
11+
rescue ZeroDivisionError => exception
12+
exception
13+
end
14+
15+
TestApp.configure do |config|
16+
config.middleware.delete ActionDispatch::DebugExceptions
17+
config.middleware.delete ActionDispatch::ShowExceptions
18+
end
19+
20+
Raven.configure do |config|
21+
config.logger = Logger.new(nil)
22+
config.dsn = "dummy://12345:[email protected]:3000/sentry/42"
23+
config.async = lambda { |event| event.to_hash } # Simulate throwing this into a Sidekiq job, etc
24+
end
25+
26+
TestApp.initialize!
27+
@app = Rack::MockRequest.new(TestApp)
28+
RAILS_EXC = begin
29+
@app.get("/exception")
30+
rescue => exc
31+
exc
32+
end
33+
34+
DIVIDE_BY_ZERO = build_exception
35+
36+
LOGGER = Logger.new(nil)
37+
38+
Benchmark.ipsa do |x|
39+
x.config(:time => 5, :warmup => 2)
40+
41+
x.report("simple") { Raven.capture_exception(DIVIDE_BY_ZERO) }
42+
x.report("rails") { Raven.capture_exception(RAILS_EXC) }
43+
x.report("lots o logs") do
44+
100.times { LOGGER.debug(rand(100_000).to_s) }
45+
Raven.capture_exception(DIVIDE_BY_ZERO)
46+
end
47+
48+
x.compare!
49+
end

benchmarks/json.rb

Lines changed: 0 additions & 22 deletions
This file was deleted.

benchmarks/profile.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require 'raven'
2+
require 'raven/breadcrumbs/logger'
3+
require 'raven/transports/dummy'
4+
require_relative "../spec/support/test_rails_app/app"
5+
6+
TestApp.configure do |config|
7+
config.middleware.delete ActionDispatch::DebugExceptions
8+
config.middleware.delete ActionDispatch::ShowExceptions
9+
end
10+
11+
Raven.configure do |config|
12+
config.logger = Logger.new(nil)
13+
config.dsn = "dummy://12345:[email protected]:3000/sentry/42"
14+
end
15+
16+
TestApp.initialize!
17+
@app = Rack::MockRequest.new(TestApp)
18+
RAILS_EXC = begin
19+
@app.get("/exception")
20+
rescue => exc
21+
exc
22+
end
23+
24+
require 'ruby-prof'
25+
26+
RubyProf.measure_mode = RubyProf::PROCESS_TIME
27+
28+
# profile the code
29+
result = RubyProf.profile do
30+
100.times { Raven.capture_exception(RAILS_EXC) }
31+
end
32+
33+
# print a graph profile to text
34+
printer = RubyProf::MultiPrinter.new(result)
35+
printer.print(:path => "./tmp", :profile => "profile")

lib/raven/client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def transport
6464

6565
def encode(event)
6666
hash = @processors.reduce(event.to_hash) { |a, e| e.process(a) }
67-
encoded = JSON.generate(hash)
67+
encoded = JSON.fast_generate(hash)
6868

6969
case configuration.encoding
7070
when 'gzip'

lib/raven/interfaces/stack_trace.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def initialize(*arguments)
3737
end
3838

3939
def filename
40-
return nil if abs_path.nil?
40+
return if abs_path.nil?
4141

4242
prefix =
4343
if under_project_root? && in_app
@@ -60,7 +60,7 @@ def project_root
6060
end
6161

6262
def longest_load_path
63-
$LOAD_PATH.select { |s| abs_path.start_with?(s.to_s) }.sort_by { |s| s.to_s.length }.last
63+
@longest_load_path ||= $LOAD_PATH.select { |s| abs_path.start_with?(s) }.max_by(&:length)
6464
end
6565

6666
def to_hash(*args)

lib/raven/processor/removecircularreferences.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
module Raven
22
class Processor::RemoveCircularReferences < Processor
3-
def process(v, visited = [])
4-
return "(...)" if visited.include?(v.__id__)
5-
visited += [v.__id__]
6-
if v.is_a?(Hash)
7-
v.each_with_object({}) { |(k, v_), memo| memo[k] = process(v_, visited) }
8-
elsif v.is_a?(Array)
9-
v.map { |v_| process(v_, visited) }
3+
def process(value, visited = [])
4+
return "(...)" if visited.include?(value.__id__)
5+
visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
6+
7+
case value
8+
when Hash
9+
!value.frozen? ? value.merge!(value) { |_, v| process v, visited } : value.merge(value) { |_, v| process v, visited }
10+
when Array
11+
!value.frozen? ? value.map! { |v| process v, visited } : value.map { |v| process v, visited }
1012
else
11-
v
13+
value
1214
end
1315
end
1416
end

lib/raven/processor/sanitizedata.rb

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,27 @@ def initialize(client)
1414
self.sanitize_credit_cards = client.configuration.sanitize_credit_cards
1515
end
1616

17-
def process(value)
18-
return value if value.frozen?
19-
value.each_with_object(value) { |(k, v), memo| memo[k] = sanitize(k, v) }
20-
end
21-
22-
def sanitize(k, v)
23-
if v.is_a?(Hash)
24-
process(v)
25-
elsif v.is_a?(Array)
26-
v.map { |a| sanitize(k, a) }
27-
elsif k.to_s == 'query_string'
28-
sanitize_query_string(v)
29-
elsif v.is_a?(Integer) && matches_regexes?(k, v)
30-
INT_MASK
31-
elsif v.is_a?(String)
32-
if fields_re.match(v.to_s) && (json = parse_json_or_nil(v))
17+
def process(value, key = nil)
18+
case value
19+
when Hash
20+
!value.frozen? ? value.merge!(value) { |k, v| process v, k } : value.merge(value) { |k, v| process v, k }
21+
when Array
22+
!value.frozen? ? value.map! { |v| process v, key } : value.map { |v| process v, key }
23+
when Integer
24+
matches_regexes?(key, value.to_s) ? INT_MASK : value
25+
when String
26+
if value =~ fields_re && (json = parse_json_or_nil(value))
3327
# if this string is actually a json obj, convert and sanitize
34-
json.is_a?(Hash) ? process(json).to_json : v
35-
elsif matches_regexes?(k, v)
28+
process(json).to_json
29+
elsif matches_regexes?(key, value)
3630
STRING_MASK
31+
elsif key == 'query_string' || key == :query_string
32+
sanitize_query_string(value)
3733
else
38-
v
34+
value
3935
end
4036
else
41-
v
37+
value
4238
end
4339
end
4440

@@ -51,8 +47,8 @@ def sanitize_query_string(query_string)
5147
end
5248

5349
def matches_regexes?(k, v)
54-
(sanitize_credit_cards && CREDIT_CARD_RE.match(v.to_s)) ||
55-
fields_re.match(k.to_s)
50+
(sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
51+
k =~ fields_re
5652
end
5753

5854
def fields_re
@@ -70,6 +66,7 @@ def special_characters?(string)
7066
end
7167

7268
def parse_json_or_nil(string)
69+
return unless string.start_with?("[", "{")
7370
JSON.parse(string)
7471
rescue JSON::ParserError, NoMethodError
7572
nil

0 commit comments

Comments
 (0)