-
-
Notifications
You must be signed in to change notification settings - Fork 533
perf: Reduce memory allocations by ~53% during exception capture #2901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,40 +23,121 @@ def inspect | |
| private | ||
|
|
||
| # Not actually an interface, but I want to use the same style | ||
| # Cache for longest_load_path lookups — shared across all frames | ||
| @load_path_cache = {} | ||
| @load_path_size = nil | ||
| # Cache for compute_filename results — many frames share identical abs_paths | ||
| # Separate caches for in_app=true and in_app=false to avoid composite keys | ||
| @filename_cache_in_app = {} | ||
| @filename_cache_not_in_app = {} | ||
| @filename_project_root = nil | ||
|
|
||
| class << self | ||
| def check_load_path_freshness | ||
| current_size = $LOAD_PATH.size | ||
| if @load_path_size != current_size | ||
| @load_path_cache = {} | ||
| @filename_cache_in_app = {} | ||
| @filename_cache_not_in_app = {} | ||
| @load_path_size = current_size | ||
| end | ||
| end | ||
|
|
||
| def longest_load_path_for(abs_path) | ||
| check_load_path_freshness | ||
|
|
||
| @load_path_cache.fetch(abs_path) do | ||
| result = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size) | ||
| @load_path_cache[abs_path] = result | ||
| end | ||
| end | ||
|
|
||
| def cached_filename(abs_path, project_root, in_app, strip_backtrace_load_path) | ||
| return abs_path unless abs_path | ||
| return abs_path unless strip_backtrace_load_path | ||
|
|
||
| check_load_path_freshness | ||
|
|
||
| # Invalidate filename cache when project_root changes | ||
| if @filename_project_root != project_root | ||
| @filename_cache_in_app = {} | ||
| @filename_cache_not_in_app = {} | ||
| @filename_project_root = project_root | ||
| end | ||
|
|
||
| cache = in_app ? @filename_cache_in_app : @filename_cache_not_in_app | ||
| cache.fetch(abs_path) do | ||
| under_root = project_root && abs_path.start_with?(project_root) | ||
| prefix = | ||
| if under_root && in_app | ||
| project_root | ||
| elsif under_root | ||
| longest_load_path_for(abs_path) || project_root | ||
| else | ||
| longest_load_path_for(abs_path) | ||
| end | ||
|
|
||
| result = if prefix | ||
| prefix_str = prefix.to_s | ||
| offset = if prefix_str.end_with?(File::SEPARATOR) | ||
| prefix_str.length | ||
| else | ||
| prefix_str.length + 1 | ||
| end | ||
| abs_path.byteslice(offset, abs_path.bytesize - offset) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. byteslice uses character length instead of byte lengthMedium Severity
Additional Locations (1) |
||
| else | ||
| abs_path | ||
| end | ||
|
|
||
| cache[abs_path] = result | ||
| end | ||
| end | ||
| end | ||
|
|
||
| class Frame < Interface | ||
| attr_accessor :abs_path, :context_line, :function, :in_app, :filename, | ||
| :lineno, :module, :pre_context, :post_context, :vars | ||
|
|
||
| def initialize(project_root, line, strip_backtrace_load_path = true) | ||
| @project_root = project_root | ||
| @strip_backtrace_load_path = strip_backtrace_load_path | ||
|
|
||
| @abs_path = line.file | ||
| @function = line.method if line.method | ||
| @lineno = line.number | ||
| @in_app = line.in_app | ||
| @module = line.module_name if line.module_name | ||
| @filename = compute_filename | ||
| @filename = StacktraceInterface.cached_filename( | ||
| @abs_path, project_root, @in_app, strip_backtrace_load_path | ||
| ) | ||
| end | ||
|
|
||
| def to_s | ||
| "#{@filename}:#{@lineno}" | ||
| end | ||
|
|
||
| def compute_filename | ||
| def compute_filename(project_root, strip_backtrace_load_path) | ||
| return if abs_path.nil? | ||
| return abs_path unless @strip_backtrace_load_path | ||
| return abs_path unless strip_backtrace_load_path | ||
|
|
||
| under_root = project_root && abs_path.start_with?(project_root) | ||
| prefix = | ||
| if under_project_root? && in_app | ||
| @project_root | ||
| elsif under_project_root? | ||
| longest_load_path || @project_root | ||
| if under_root && in_app | ||
| project_root | ||
| elsif under_root | ||
| longest_load_path || project_root | ||
| else | ||
| longest_load_path | ||
| end | ||
|
|
||
| prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path | ||
| if prefix | ||
| prefix_str = prefix.to_s | ||
| offset = if prefix_str.end_with?(File::SEPARATOR) | ||
| prefix_str.length | ||
| else | ||
| prefix_str.length + 1 | ||
| end | ||
| abs_path.byteslice(offset, abs_path.bytesize - offset) | ||
| else | ||
| abs_path | ||
| end | ||
| end | ||
|
|
||
| def set_context(linecache, context_lines) | ||
|
|
@@ -77,12 +158,8 @@ def to_h(*args) | |
|
|
||
| private | ||
|
|
||
| def under_project_root? | ||
| @project_root && abs_path.start_with?(@project_root) | ||
| end | ||
|
|
||
| def longest_load_path | ||
| $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size) | ||
| StacktraceInterface.longest_load_path_for(abs_path) | ||
| end | ||
| end | ||
| end | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in_app_pattern cache ignores project_root in key
Medium Severity
The
@in_app_pattern_cacheuses onlyapp_dirs_patternas the cache key, but the constructedRegexpembedsproject_root. Ifproject_rootchanges whileapp_dirs_patternstays the same (e.g., re-initialization or multiple clients), the cache returns a stale pattern containing the oldproject_root, causing incorrect in-app classification.