-
-
Notifications
You must be signed in to change notification settings - Fork 165
Add solargraph profile command using vernier
#1071
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
base: master
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -241,6 +241,107 @@ def list | |
| puts "#{workspace.filenames.length} files total." | ||
| end | ||
|
|
||
| desc 'profile [FILE]', 'Profile go-to-definition performance using vernier' | ||
| option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.' | ||
| option :output_dir, type: :string, aliases: :o, desc: 'The output directory for profiles', default: './tmp/profiles' | ||
| option :line, type: :numeric, aliases: :l, desc: 'Line number (0-based)', default: 4 | ||
| option :column, type: :numeric, aliases: :c, desc: 'Column number', default: 10 | ||
| option :memory, type: :boolean, aliases: :m, desc: 'Include memory usage counter', default: true | ||
| # @param file [String, nil] | ||
| # @return [void] | ||
| def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength | ||
| begin | ||
| require 'vernier' | ||
| rescue LoadError | ||
| STDERR.puts "vernier gem not found. Install with: gem install vernier" | ||
| return | ||
| end | ||
|
|
||
| hooks = [] | ||
| hooks << :memory_usage if options[:memory] | ||
|
|
||
| directory = File.realpath(options[:directory]) | ||
| FileUtils.mkdir_p(options[:output_dir]) | ||
|
|
||
| host = Solargraph::LanguageServer::Host.new | ||
| host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } }) | ||
| # @param method [String] The message method | ||
| # @param params [Hash] The method parameters | ||
| # @return [void] | ||
| def host.send_notification method, params | ||
| puts "Notification: #{method} - #{params}" | ||
| end | ||
|
Comment on lines
+271
to
+273
Contributor
Author
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. I thought I'd never find a use-case for this Ruby feature, but I finally found one 😍
Contributor
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. Hey! This is the line that triggered that assert - looks like Solargraph has never run through that code in its tests or typechecking in CI, either! https://github.com/apiology/solargraph/blob/master/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb creates some pins, but doesn't pass the 'source' kwarg in. If you throw "source: :parser" into that "Solargraph::Pin::Namespace.new" call, it should get past that error.
Contributor
Author
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. Thanks again, @apiology, for coming to the rescue! 🙇♂️ For some reason, I thought the typechecker does not cover the shell area. |
||
|
|
||
| puts "Parsing and mapping source files..." | ||
| prepare_start = Time.now | ||
| Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do | ||
| puts "Mapping libraries" | ||
| host.prepare(directory) | ||
| sleep 0.2 until host.libraries.all?(&:mapped?) | ||
| end | ||
| prepare_time = Time.now - prepare_start | ||
|
|
||
| puts "Building the catalog..." | ||
| catalog_start = Time.now | ||
| Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do | ||
| host.catalog | ||
| end | ||
| catalog_time = Time.now - catalog_start | ||
|
|
||
| # Determine test file | ||
| if file | ||
| test_file = File.join(directory, file) | ||
| else | ||
| test_file = File.join(directory, 'lib', 'other.rb') | ||
| unless File.exist?(test_file) | ||
| # Fallback to any Ruby file in the workspace | ||
| workspace = Solargraph::Workspace.new(directory) | ||
| test_file = workspace.filenames.find { |f| f.end_with?('.rb') } | ||
| unless test_file | ||
| STDERR.puts "No Ruby files found in workspace" | ||
| return | ||
| end | ||
| end | ||
| end | ||
|
|
||
| file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file)) | ||
|
|
||
| puts "Profiling go-to-definition for #{test_file}" | ||
| puts "Position: line #{options[:line]}, column #{options[:column]}" | ||
|
|
||
| definition_start = Time.now | ||
| Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do | ||
| message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( | ||
| host, { | ||
| 'params' => { | ||
| 'textDocument' => { 'uri' => file_uri }, | ||
| 'position' => { 'line' => options[:line], 'character' => options[:column] } | ||
| } | ||
| } | ||
| ) | ||
| puts "Processing go-to-definition request..." | ||
| result = message.process | ||
|
|
||
| puts "Result: #{result.inspect}" | ||
| end | ||
| definition_time = Time.now - definition_start | ||
|
|
||
| puts "\n=== Timing Results ===" | ||
| puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" | ||
| puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" | ||
| puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" | ||
| total_time = prepare_time + catalog_time + definition_time | ||
| puts "Total time: #{(total_time * 1000).round(2)}ms" | ||
|
|
||
| puts "\nProfiles saved to:" | ||
| puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}" | ||
| puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}" | ||
| puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}" | ||
|
|
||
| puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." | ||
| puts "Or use https://rubygems.org/gems/profile-viewer to view them locally." | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # @param pin [Solargraph::Pin::Base] | ||
|
|
||
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.
I have based the host setup, based on this spec:
solargraph/spec/language_server/message/text_document/definition_spec.rb
Line 2 in 872431c
Is there any critical step missing that should be included in the profile?