Skip to content

Commit 4d02e76

Browse files
committed
Add solargraph profile command
1 parent 487381c commit 4d02e76

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

lib/solargraph/shell.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,100 @@ def list
241241
puts "#{workspace.filenames.length} files total."
242242
end
243243

244+
desc 'profile [FILE]', 'Profile go-to-definition performance using vernier'
245+
option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.'
246+
option :output_dir, type: :string, aliases: :o, desc: 'The output directory for profiles', default: './tmp/profiles'
247+
option :line, type: :numeric, aliases: :l, desc: 'Line number (0-based)', default: 4
248+
option :column, type: :numeric, aliases: :c, desc: 'Column number', default: 10
249+
# @param file [String, nil]
250+
# @return [void]
251+
def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
252+
begin
253+
require 'vernier'
254+
rescue LoadError
255+
STDERR.puts "vernier gem not found. Install with: gem install vernier"
256+
return
257+
end
258+
259+
directory = File.realpath(options[:directory])
260+
FileUtils.mkdir_p(options[:output_dir])
261+
262+
host = Solargraph::LanguageServer::Host.new
263+
host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } })
264+
def host.send_notification method, params
265+
puts "Notification: #{method} - #{params}"
266+
end
267+
268+
puts "Parsing and mapping source files..."
269+
prepare_start = Time.now
270+
Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz") do
271+
puts "Mapping libraries"
272+
host.prepare(directory)
273+
sleep 0.2 until host.libraries.all?(&:mapped?)
274+
end
275+
prepare_time = Time.now - prepare_start
276+
277+
puts "Building the catalog..."
278+
catalog_start = Time.now
279+
Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz") do
280+
host.catalog
281+
end
282+
catalog_time = Time.now - catalog_start
283+
284+
# Determine test file
285+
if file
286+
test_file = File.join(directory, file)
287+
else
288+
test_file = File.join(directory, 'lib', 'other.rb')
289+
unless File.exist?(test_file)
290+
# Fallback to any Ruby file in the workspace
291+
workspace = Solargraph::Workspace.new(directory)
292+
test_file = workspace.filenames.find { |f| f.end_with?('.rb') }
293+
unless test_file
294+
STDERR.puts "No Ruby files found in workspace"
295+
return
296+
end
297+
end
298+
end
299+
300+
file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file))
301+
302+
puts "Profiling go-to-definition for #{test_file}"
303+
puts "Position: line #{options[:line]}, column #{options[:column]}"
304+
305+
definition_start = Time.now
306+
Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz") do
307+
message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(
308+
host, {
309+
'params' => {
310+
'textDocument' => { 'uri' => file_uri },
311+
'position' => { 'line' => options[:line], 'character' => options[:column] }
312+
}
313+
}
314+
)
315+
puts "Processing go-to-definition request..."
316+
result = message.process
317+
318+
puts "Result: #{result.inspect}"
319+
end
320+
definition_time = Time.now - definition_start
321+
322+
puts "\n=== Timing Results ==="
323+
puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms"
324+
puts "Catalog building: #{(catalog_time * 1000).round(2)}ms"
325+
puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms"
326+
total_time = prepare_time + catalog_time + definition_time
327+
puts "Total time: #{(total_time * 1000).round(2)}ms"
328+
329+
puts "\nProfiles saved to:"
330+
puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}"
331+
puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}"
332+
puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}"
333+
334+
puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles."
335+
puts "Or use https://rubygems.org/gems/profile-viewer to view them locally."
336+
end
337+
244338
private
245339

246340
# @param pin [Solargraph::Pin::Base]

solargraph.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Gem::Specification.new do |s|
5959
s.add_development_dependency 'undercover', '~> 0.7'
6060
s.add_development_dependency 'overcommit', '~> 0.68.0'
6161
s.add_development_dependency 'webmock', '~> 3.6'
62+
s.add_development_dependency 'vernier'
6263
# work around missing yard dependency needed as of Ruby 3.5
6364
s.add_development_dependency 'irb', '~> 1.15'
6465
end

0 commit comments

Comments
 (0)