Class: Contrast::Agent::Inventory::DependencyUsageAnalysis

Inherits:
Object
  • Object
show all
Includes:
Dependencies, Components::Logger::InstanceMethods, Singleton
Defined in:
lib/contrast/agent/inventory/dependency_usage_analysis.rb

Overview

Used to analyze class usage for reporting

Constant Summary

Constants included from Dependencies

Contrast::Agent::Inventory::Dependencies::CONTRAST_AGENT

Instance Method Summary collapse

Methods included from Dependencies

#loaded_specs

Methods included from Components::Logger::InstanceMethods

#cef_logger, #logger

Constructor Details

#initializeDependencyUsageAnalysis

Returns a new instance of DependencyUsageAnalysis.



19
20
21
22
23
24
# File 'lib/contrast/agent/inventory/dependency_usage_analysis.rb', line 19

def initialize
  return unless enabled?

  @lock = Mutex.new
  @lock.synchronize { @gemdigest_cache = Hash.new { |hash, key| hash[key] = Set.new } }
end

Instance Method Details

#associate_file(path) ⇒ Object

This method is invoked once per TracePoint :end - to map a specific file being required to the gem to which it belongs.

Parameters:

  • path (String)

    the result of TracePoint#path from the :end event in which the Module was defined.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/contrast/agent/inventory/dependency_usage_analysis.rb', line 51

def associate_file path
  return unless enabled?

  spec_lookup_path = adjust_path_for_spec_lookup(path)
  spec = Gem::Specification.find_by_path(spec_lookup_path)
  unless spec
    logger.debug('Unable to resolve gem spec for path', path: path)
    return
  end

  digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
  unless digest
    logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s) if logger.debug?
    return
  end
  report_path = adjust_path_for_reporting(path, spec)
  @lock.synchronize { @gemdigest_cache[digest] << report_path }
rescue StandardError => e
  logger.error('Unable to inventory file path', e, path: path)
end

#catchupObject

This method is invoked once, along with the rest of our catchup code to report libraries and their associated files that have already been loaded pre-contrast.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/contrast/agent/inventory/dependency_usage_analysis.rb', line 28

def catchup
  return unless enabled?

  loaded_specs.each do |_name, spec|
    # Get a digest of the Gem file itself.
    next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))

    loaded_files_from_gem = $LOADED_FEATURES.select { |f| f.start_with?(spec.full_gem_path) }

    new_files = loaded_files_from_gem.each_with_object(Set.new) do |file_path, set|
      set << adjust_path_for_reporting(file_path, spec)
      logger.trace('Recording loaded file for inventory analysis', line: file_path)
    end

    # Even if new_files is empty, still need to add digest key for library discovery.
    @lock.synchronize { @gemdigest_cache[digest].merge(new_files) }
  end
end

#generate_library_usageContrast::Agent::Reporting::ObservedLibraryUsage?

Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache. If no libraries had files loaded, or inventory analysis is disabled, return nil instead.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/contrast/agent/inventory/dependency_usage_analysis.rb', line 76

def generate_library_usage
  return unless enabled?
  return unless @gemdigest_cache.any?

  # Disconnect gemdigest_cache and replace it with an empty one; synch so new libs cannot be added between the
  # assignment and the replace
  gem_spec_digest_to_files = @lock.synchronize do
    hold = @gemdigest_cache
    @gemdigest_cache = Hash.new { |hash, key| hash[key] = Set.new }
    hold
  end

  observed_library_usage = Contrast::Agent::Reporting::ObservedLibraryUsage.new
  gem_spec_digest_to_files.each_pair do |digest, files|
    next unless files.any?

    usage = Contrast::Agent::Reporting::LibraryUsageObservation.new(digest, files)
    observed_library_usage.observations << usage
  end
  observed_library_usage.observations.any? ? observed_library_usage : nil
rescue StandardError => e
  logger.error('Unable to generate library usage.', e)
end