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:

  • 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.

Returns:



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