Skip to content

Commit 398da01

Browse files
HazATsl0thentr0py
authored andcommitted
perf: cache longest_load_path and compute_filename results
Add class-level caches to StacktraceInterface for two expensive per-frame operations that repeat with identical inputs: longest_load_path: Previously iterated $LOAD_PATH for every frame, creating many intermediate strings. Now cached by abs_path with automatic invalidation when $LOAD_PATH.size changes (e.g. after Bundler.require). compute_filename: Many frames share identical abs_paths (same gem files appear in every exception). Results are cached in separate in_app/ not_in_app hashes keyed by abs_path only, avoiding composite array keys. Cache invalidates on project_root or $LOAD_PATH changes. Both caches are deterministic — same inputs always produce the same filename. The caches grow proportionally to the number of unique source files seen, which is naturally bounded in any application.
1 parent 688bd62 commit 398da01

File tree

1 file changed

+75
-2
lines changed

1 file changed

+75
-2
lines changed

sentry-ruby/lib/sentry/interfaces/stacktrace.rb

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,77 @@ def inspect
2323
private
2424

2525
# Not actually an interface, but I want to use the same style
26+
# Cache for longest_load_path lookups — shared across all frames
27+
@load_path_cache = {}
28+
@load_path_size = nil
29+
# Cache for compute_filename results — many frames share identical abs_paths
30+
# Separate caches for in_app=true and in_app=false to avoid composite keys
31+
@filename_cache_in_app = {}
32+
@filename_cache_not_in_app = {}
33+
@filename_project_root = nil
34+
35+
class << self
36+
def check_load_path_freshness
37+
current_size = $LOAD_PATH.size
38+
if @load_path_size != current_size
39+
@load_path_cache = {}
40+
@filename_cache_in_app = {}
41+
@filename_cache_not_in_app = {}
42+
@load_path_size = current_size
43+
end
44+
end
45+
46+
def longest_load_path_for(abs_path)
47+
check_load_path_freshness
48+
49+
@load_path_cache.fetch(abs_path) do
50+
result = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
51+
@load_path_cache[abs_path] = result
52+
end
53+
end
54+
55+
def cached_filename(abs_path, project_root, in_app, strip_backtrace_load_path)
56+
return abs_path unless abs_path
57+
return abs_path unless strip_backtrace_load_path
58+
59+
check_load_path_freshness
60+
61+
# Invalidate filename cache when project_root changes
62+
if @filename_project_root != project_root
63+
@filename_cache_in_app = {}
64+
@filename_cache_not_in_app = {}
65+
@filename_project_root = project_root
66+
end
67+
68+
cache = in_app ? @filename_cache_in_app : @filename_cache_not_in_app
69+
cache.fetch(abs_path) do
70+
under_root = project_root && abs_path.start_with?(project_root)
71+
prefix =
72+
if under_root && in_app
73+
project_root
74+
elsif under_root
75+
longest_load_path_for(abs_path) || project_root
76+
else
77+
longest_load_path_for(abs_path)
78+
end
79+
80+
result = if prefix
81+
prefix_str = prefix.to_s
82+
offset = if prefix_str.end_with?(File::SEPARATOR)
83+
prefix_str.length
84+
else
85+
prefix_str.length + 1
86+
end
87+
abs_path.byteslice(offset, abs_path.bytesize - offset)
88+
else
89+
abs_path
90+
end
91+
92+
cache[abs_path] = result
93+
end
94+
end
95+
end
96+
2697
class Frame < Interface
2798
attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
2899
:lineno, :module, :pre_context, :post_context, :vars
@@ -33,7 +104,9 @@ def initialize(project_root, line, strip_backtrace_load_path = true)
33104
@lineno = line.number
34105
@in_app = line.in_app
35106
@module = line.module_name if line.module_name
36-
@filename = compute_filename(project_root, strip_backtrace_load_path)
107+
@filename = StacktraceInterface.cached_filename(
108+
@abs_path, project_root, @in_app, strip_backtrace_load_path
109+
)
37110
end
38111

39112
def to_s
@@ -86,7 +159,7 @@ def to_h(*args)
86159
private
87160

88161
def longest_load_path
89-
$LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
162+
StacktraceInterface.longest_load_path_for(abs_path)
90163
end
91164
end
92165
end

0 commit comments

Comments
 (0)