diff --git a/Steepfile b/Steepfile index 02ad04fef04..13f902fd098 100644 --- a/Steepfile +++ b/Steepfile @@ -224,6 +224,7 @@ target :datadog do # References `RubyVM::YJIT`, which does not have type information. ignore 'lib/datadog/core/environment/yjit.rb' + library 'bundler' library 'pathname' library 'cgi' library 'logger', 'monitor' diff --git a/lib/datadog/profiling/collectors/code_provenance.rb b/lib/datadog/profiling/collectors/code_provenance.rb index 0d7f7f048b1..b9236620c44 100644 --- a/lib/datadog/profiling/collectors/code_provenance.rb +++ b/lib/datadog/profiling/collectors/code_provenance.rb @@ -22,6 +22,7 @@ def initialize( @libraries_by_path = {} @seen_files = Set.new @seen_libraries = Set.new + @executable_paths = [Gem.bindir, (Bundler.bin_path.to_s if defined?(Bundler))].uniq.compact.freeze record_library( Library.new( @@ -29,7 +30,7 @@ def initialize( name: "stdlib", version: RUBY_VERSION, path: standard_library_path, - extra_path: ruby_native_filename, + extra_paths: [ruby_native_filename], ) ) end @@ -51,7 +52,8 @@ def generate_json :libraries_by_name, :libraries_by_path, :seen_files, - :seen_libraries + :seen_libraries, + :executable_paths def record_library(library) libraries_by_name[library.name] = library @@ -79,13 +81,20 @@ def record_loaded_specs(loaded_specs) loaded_specs.each do |spec| next if libraries_by_name.key?(spec.name) + extra_paths = [(spec.extension_dir if spec.extensions.any?)] + spec.executables&.each do |executable| + executable_paths.each do |path| + extra_paths << File.join(path, executable) + end + end + record_library( Library.new( kind: "library", name: spec.name, version: spec.version, path: spec.gem_dir, - extra_path: (spec.extension_dir if spec.extensions.any?), + extra_paths: extra_paths, ) ) recorded_library = true @@ -118,12 +127,12 @@ def record_loaded_files(loaded_files) class Library attr_reader :kind, :name, :version - def initialize(kind:, name:, version:, path:, extra_path: nil) - extra_path = nil if extra_path&.empty? + def initialize(kind:, name:, version:, path:, extra_paths:) + extra_paths = Array(extra_paths).compact.reject(&:empty?).map { |p| p.dup.freeze } @kind = kind.freeze @name = name.dup.freeze @version = version.to_s.dup.freeze - @paths = [path.dup.freeze, extra_path.dup.freeze].compact.freeze + @paths = [path.dup.freeze, *extra_paths].freeze freeze end diff --git a/sig/datadog/profiling/collectors/code_provenance.rbs b/sig/datadog/profiling/collectors/code_provenance.rbs index 20903dd32b7..1bc6ce0ac05 100644 --- a/sig/datadog/profiling/collectors/code_provenance.rbs +++ b/sig/datadog/profiling/collectors/code_provenance.rbs @@ -12,6 +12,7 @@ module Datadog attr_reader libraries_by_path: Hash[String, Library] attr_reader seen_files: Set[String] attr_reader seen_libraries: Set[Library] + attr_reader executable_paths: Array[String] def record_library: (Library) -> void def sort_libraries_by_longest_path_first: () -> void @@ -24,7 +25,7 @@ module Datadog @version: String @paths: Array[String] - def initialize: (kind: String, name: String, version: String, path: String, ?extra_path: String?) -> void + def initialize: (kind: String, name: String, version: String, path: String, extra_paths: Array[String?]) -> void def kind: -> String def name: -> String diff --git a/spec/datadog/profiling/collectors/code_provenance_spec.rb b/spec/datadog/profiling/collectors/code_provenance_spec.rb index c8b285c9707..ce111a7de88 100644 --- a/spec/datadog/profiling/collectors/code_provenance_spec.rb +++ b/spec/datadog/profiling/collectors/code_provenance_spec.rb @@ -32,11 +32,16 @@ kind: "library", name: "datadog", version: Datadog::VERSION::STRING, - paths: contain_exactly(start_with("/"), include("extensions").and(include(RUBY_PLATFORM))), + paths: contain_exactly( + start_with("/"), + include("extensions").and(include(RUBY_PLATFORM)), + "#{Gem.bindir}/ddprofrb", + "#{Bundler.bin_path}/ddprofrb", + ), }, { kind: "library", - name: "rspec-core", + name: "rspec", version: start_with("3."), # This will one day need to be bumped for RSpec 4 paths: contain_exactly(start_with("/")), }, @@ -59,6 +64,20 @@ ) end + it "includes the executables for gems with executables" do + refresh + + expect(generate_result.find { |it| it[:name] == "rspec-core" }.fetch(:paths)).to contain_exactly( + Gem.loaded_specs.fetch("rspec-core").gem_dir, + "#{Gem.bindir}/rspec", + "#{Bundler.bin_path}/rspec", + ) + + # Sanity checks + expect(Gem.bindir).to start_with("/") + expect(Bundler.bin_path.to_s).to start_with("/") + end + it "records the correct path for datadog" do refresh @@ -78,6 +97,7 @@ version: "not_loaded_version", gem_dir: "/not_loaded/", extensions: [], + executables: [], ), instance_double( Gem::Specification, @@ -85,6 +105,7 @@ version: "is_loaded_version", gem_dir: "/is_loaded/", extensions: [], + executables: [], ) ], ) @@ -131,6 +152,7 @@ version: "1.2.3", gem_dir: "/dd-trace-rb", extensions: [], + executables: [], ), instance_double( Gem::Specification, @@ -138,6 +160,7 @@ version: "4.5.6", gem_dir: "/dd-trace-rb/vendor/bundle/ruby/2.7.0/gems/byebug-11.1.3", extensions: [], + executables: [], ) ], ) @@ -288,6 +311,7 @@ kind: "library", version: "1.2.3", path: "/example/path/to/datadog/gem", + extra_paths: ["/example/path/to/datadog/extensions"], ) serialized_without_to_json = YAML.dump(instance) @@ -298,7 +322,7 @@ "name" => "datadog", "kind" => "library", "version" => "1.2.3", - "paths" => ["/example/path/to/datadog/gem"], + "paths" => ["/example/path/to/datadog/gem", "/example/path/to/datadog/extensions"], ) end end diff --git a/vendor/rbs/bundler/0/bundler.rbs b/vendor/rbs/bundler/0/bundler.rbs new file mode 100644 index 00000000000..dad997a83dc --- /dev/null +++ b/vendor/rbs/bundler/0/bundler.rbs @@ -0,0 +1,3 @@ +module Bundler + def self.bin_path: () -> Pathname +end diff --git a/vendor/rbs/gem/0/gem.rbs b/vendor/rbs/gem/0/gem.rbs index 551e8ad1169..a61606019a9 100644 --- a/vendor/rbs/gem/0/gem.rbs +++ b/vendor/rbs/gem/0/gem.rbs @@ -16,4 +16,5 @@ class Gem::BasicSpecification def gem_dir: () -> String def extension_dir: () -> String def extensions: () -> Array[String] + def executables: () -> Array[String]? end