contrast-agent 4.2.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent.rb +5 -1
  6. data/lib/contrast/agent/assess.rb +0 -9
  7. data/lib/contrast/agent/assess/contrast_event.rb +49 -132
  8. data/lib/contrast/agent/assess/contrast_object.rb +54 -0
  9. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  11. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
  12. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  13. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  14. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +41 -32
  16. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  17. data/lib/contrast/agent/assess/policy/propagator/append.rb +29 -15
  18. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  19. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -18
  21. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  23. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  24. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  26. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  27. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  28. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  29. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  30. data/lib/contrast/agent/assess/policy/propagator/splat.rb +25 -17
  31. data/lib/contrast/agent/assess/policy/propagator/split.rb +83 -120
  32. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +41 -25
  33. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  34. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  35. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  36. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  37. data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
  38. data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
  39. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
  40. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -3
  41. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
  42. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
  43. data/lib/contrast/agent/assess/properties.rb +0 -2
  44. data/lib/contrast/agent/assess/property/tagged.rb +56 -32
  45. data/lib/contrast/agent/assess/tracker.rb +16 -18
  46. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  47. data/lib/contrast/agent/middleware.rb +134 -55
  48. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +4 -0
  49. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  50. data/lib/contrast/agent/patching/policy/patch.rb +4 -4
  51. data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
  52. data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
  53. data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
  54. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  55. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  56. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  57. data/lib/contrast/agent/protect/rule/cmd_injection.rb +3 -3
  58. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  59. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  60. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  61. data/lib/contrast/agent/protect/rule/sqli.rb +20 -14
  62. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  63. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  64. data/lib/contrast/agent/reaction_processor.rb +1 -1
  65. data/lib/contrast/agent/request_context.rb +12 -0
  66. data/lib/contrast/agent/response.rb +5 -5
  67. data/lib/contrast/agent/rewriter.rb +3 -3
  68. data/lib/contrast/agent/scope.rb +33 -13
  69. data/lib/contrast/agent/static_analysis.rb +13 -7
  70. data/lib/contrast/agent/thread.rb +1 -1
  71. data/lib/contrast/agent/thread_watcher.rb +20 -5
  72. data/lib/contrast/agent/version.rb +1 -1
  73. data/lib/contrast/api/communication/messaging_queue.rb +18 -21
  74. data/lib/contrast/api/communication/response_processor.rb +8 -1
  75. data/lib/contrast/api/communication/socket_client.rb +22 -14
  76. data/lib/contrast/api/decorators.rb +2 -0
  77. data/lib/contrast/api/decorators/agent_startup.rb +58 -0
  78. data/lib/contrast/api/decorators/application_startup.rb +51 -0
  79. data/lib/contrast/api/decorators/library.rb +1 -0
  80. data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
  81. data/lib/contrast/api/decorators/route_coverage.rb +15 -5
  82. data/lib/contrast/api/decorators/trace_event.rb +58 -42
  83. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  84. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  85. data/lib/contrast/api/decorators/user_input.rb +2 -1
  86. data/lib/contrast/common_agent_configuration.rb +1 -1
  87. data/lib/contrast/components/agent.rb +2 -0
  88. data/lib/contrast/components/app_context.rb +4 -22
  89. data/lib/contrast/components/assess.rb +36 -0
  90. data/lib/contrast/components/interface.rb +5 -3
  91. data/lib/contrast/components/sampling.rb +48 -6
  92. data/lib/contrast/components/scope.rb +23 -0
  93. data/lib/contrast/components/settings.rb +8 -7
  94. data/lib/contrast/config/assess_configuration.rb +2 -1
  95. data/lib/contrast/extension/assess/array.rb +1 -2
  96. data/lib/contrast/extension/assess/erb.rb +1 -3
  97. data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
  98. data/lib/contrast/extension/assess/fiber.rb +2 -3
  99. data/lib/contrast/extension/assess/hash.rb +4 -2
  100. data/lib/contrast/extension/assess/kernel.rb +1 -2
  101. data/lib/contrast/extension/assess/marshal.rb +34 -26
  102. data/lib/contrast/extension/assess/regexp.rb +3 -8
  103. data/lib/contrast/extension/assess/string.rb +1 -2
  104. data/lib/contrast/framework/base_support.rb +51 -53
  105. data/lib/contrast/framework/manager.rb +16 -14
  106. data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
  107. data/lib/contrast/framework/rack/support.rb +2 -1
  108. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  109. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  110. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  111. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  112. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  113. data/lib/contrast/framework/rails/support.rb +44 -44
  114. data/lib/contrast/framework/sinatra/support.rb +102 -42
  115. data/lib/contrast/logger/application.rb +0 -3
  116. data/lib/contrast/logger/log.rb +31 -15
  117. data/lib/contrast/utils/class_util.rb +3 -1
  118. data/lib/contrast/utils/duck_utils.rb +1 -1
  119. data/lib/contrast/utils/heap_dump_util.rb +103 -87
  120. data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
  121. data/lib/contrast/utils/object_share.rb +3 -3
  122. data/lib/contrast/utils/preflight_util.rb +1 -1
  123. data/lib/contrast/utils/resource_loader.rb +1 -1
  124. data/lib/contrast/utils/sha256_builder.rb +2 -2
  125. data/lib/contrast/utils/string_utils.rb +1 -1
  126. data/lib/contrast/utils/tag_util.rb +9 -13
  127. data/resources/assess/policy.json +12 -18
  128. data/resources/deadzone/policy.json +150 -0
  129. data/resources/protect/policy.json +12 -0
  130. data/ruby-agent.gemspec +60 -19
  131. data/service_executables/VERSION +1 -1
  132. data/service_executables/linux/contrast-service +0 -0
  133. data/service_executables/mac/contrast-service +0 -0
  134. metadata +124 -112
  135. data/lib/contrast/agent/assess/rule.rb +0 -18
  136. data/lib/contrast/agent/assess/rule/base.rb +0 -52
  137. data/lib/contrast/agent/assess/rule/redos.rb +0 -67
  138. data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
  139. data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
  140. data/lib/contrast/utils/prevent_serialization.rb +0 -52
@@ -2,14 +2,13 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/framework/base_support'
5
- require 'contrast/framework/sinatra/patch/support'
6
5
 
7
6
  module Contrast
8
7
  module Framework
9
8
  module Sinatra
10
9
  # Used when Sinatra is present to define framework specific behavior
11
- class Support < BaseSupport
12
- extend Contrast::Framework::Sinatra::Patch::Support
10
+ class Support
11
+ extend Contrast::Framework::BaseSupport
13
12
  class << self
14
13
  def detection_class
15
14
  'Sinatra'
@@ -20,44 +19,74 @@ module Contrast
20
19
  end
21
20
 
22
21
  def application_name
23
- return unless app_class
24
-
25
- app_class.cs__class.cs__name
22
+ app_class&.cs__name
26
23
  end
27
24
 
28
25
  def application_root
29
- return unless app_class
30
-
31
- app_class.root
26
+ app_instance&.root
32
27
  end
33
28
 
34
29
  def server_type
35
30
  'sinatra'
36
31
  end
37
32
 
38
- # Iterate over every class that extends Sinatra::Base, pull out its routes
39
- # (array of arrays with Mustermann::Sinatra as [][0]) and convert them into
40
- # Contrast::Api::Dtm::RouteCoverage
33
+ # Given an object, determine if it is a Sinatra controller with routes.
34
+ #
35
+ # @param app [Object] suspected Sinatra app.
36
+ # @return [Boolean]
37
+ def sinatra_controller? app
38
+ # Sinatra is loaded?
39
+ return false unless defined?(::Sinatra) && defined?(::Sinatra::Base)
40
+ # App is a subclass of or actually is ::Sinatra::Base.
41
+ return false unless (app.cs__respond_to?(:<) && app < ::Sinatra::Base) || app == ::Sinatra::Base
42
+
43
+ # App has routes.
44
+ !app.routes.empty?
45
+ end
46
+
47
+ # Find all classes that subclass ::Sinatra::Base. Gather their routes.
48
+ #
49
+ # @return [Array<Contrast::Api::Dtm::RouteCoverage>] the routes found as Dtms.
41
50
  def collect_routes
51
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless defined?(::Sinatra) && defined?(::Sinatra::Base)
52
+
42
53
  routes = []
43
- controllers = sinatra_controllers
44
- controllers.each do |clazz|
45
- class_routes = sinatra_class_routes(clazz)
46
- next unless class_routes
47
-
48
- class_routes.each_pair do |method, list|
49
- # item: [ Mustermann::Sinatra, [], Proc]
50
- list.each do |item|
51
- routes << Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(clazz, method, item[0])
54
+ sinatra_controllers.each do |controller|
55
+ controller.routes.each_pair do |method, route_triplets|
56
+ # Sinatra stores its routes as a triplet: [Mustermann::Sinatra, [], Proc]
57
+ route_triplets.map(&:first).each do |route_pattern|
58
+ routes << Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(controller, method, route_pattern)
52
59
  end
53
60
  end
54
61
  end
55
62
  routes
56
63
  end
57
64
 
58
- # TODO: RUBY-763
59
- def current_route _request
60
- nil
65
+ # Given the current request return a RouteCoverage dtm.
66
+ #
67
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
68
+ # @param controller [::Sinatra::Base] optionally use this controller instead of global ::Sinatra::Base.
69
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil] a Dtm describing the route
70
+ # matched to the request if a match was found.
71
+ def current_route request, controller = ::Sinatra::Base, full_route = nil
72
+ return unless sinatra_controller?(controller)
73
+
74
+ method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
75
+
76
+ # Find route match--checking superclasses if necessary.
77
+ final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
78
+ return unless !final_controller.nil? && !route_pattern.nil?
79
+
80
+ full_route ||= request.path_info
81
+
82
+ Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(final_controller, method, route_pattern, full_route)
83
+ end
84
+
85
+ # Search object space for sinatra controllers--any class that subclasses ::Sinatra::Base.
86
+ #
87
+ # @return [Array<::Sinatra::Base>] sinatra controlelrs
88
+ def sinatra_controllers
89
+ [::Sinatra::Base] + ObjectSpace.each_object(Class).select { |clazz| sinatra_controller?(clazz) }
61
90
  end
62
91
 
63
92
  def retrieve_request env
@@ -66,30 +95,61 @@ module Contrast
66
95
 
67
96
  private
68
97
 
69
- def app_class
70
- return nil unless defined?(::Sinatra) && defined?(::Sinatra::Base)
71
-
72
- @_app_class ||= begin
73
- sinatra_layers = ObjectSpace.each_object(::Sinatra::Base).to_a
74
- result_layer = sinatra_layers.find { |layer| layer.app.nil? }
75
- result_layer
98
+ # Given a controller and a route to match against, find the route_pattern and class that will serve the
99
+ # route. This is recursive as Sinatra's routing is recursive from subclass to super.
100
+ #
101
+ # @param controller [Sinatra::Base, #routes] a Sinatra application.
102
+ # @param method [::Rack::REQUEST_METHOD] GET, POST, PUT, etc...
103
+ # @param method [String] the relative route passed from Rack.
104
+ # @return [Array[Sinatra::Base, Mustermann::Sinatra], nil] Either the controller that
105
+ # will handle the route along with the route pattern or nil if no match.
106
+ def _route_recurse controller, method, route
107
+ return if controller.nil? || controller.cs__class == NilClass
108
+
109
+ route_patterns = controller.routes.fetch(method, []).map(&:first)
110
+ route_pattern = route_patterns&.find do |matcher|
111
+ matcher.params(route) # ::Mustermann::Sinatra match.
76
112
  end
113
+
114
+ return controller, route_pattern if route_pattern
115
+
116
+ # Check routes defined in superclass if present.
117
+ return _route_recurse(controller.superclass, method, route) if controller.superclass&.instance_variable_get(:@routes)
77
118
  end
78
119
 
79
- # Iterate over every class that extends Sinatra::Base, pull out its routes
80
- # (array of arrays with Mustermann::Sinatra as [][0]) and convert them into
81
- # Contrast::Api::Dtm::RouteCoverage
82
- def sinatra_controllers
83
- return [] unless defined?(::Sinatra) && defined?(::Sinatra::Base)
120
+ # Get route and do some cleanup matching that of Sinatra::Base#process_route.
121
+ #
122
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
123
+ # @return [String] the extracted and cleaned relative route.
124
+ def _cleaned_route request
125
+ settings = ::Sinatra::Base.settings
126
+ route = request.env[::Rack::PATH_INFO]
127
+ return '/' if route.empty? && !settings.empty_path_info?
128
+
129
+ !settings.strict_paths? && route.end_with?('/') ? route[0..-2] : route
130
+ end
131
+
132
+ # Almost an alias to app_instance.
133
+ #
134
+ # @return [::Sinatra::Base] the current controller class as routed by Rack.
135
+ def app_class
136
+ return unless defined?(::Sinatra) && defined?(::Sinatra::Base)
84
137
 
85
- Contrast::Utils::ClassUtil.descendants(::Sinatra::Base)
138
+ app_instance.cs__class
86
139
  end
87
140
 
88
- def sinatra_class_routes controller
89
- controller.instance_variable_get(:@routes) if controller.instance_variable_defined?(:@routes)
90
- rescue StandardError
91
- logger.trace('Sinatra controller found with no route instances', module: controller)
92
- nil
141
+ # Search the object space for the controller handling this request which will be
142
+ # the class inheriting from ::Sinatra::Base with @app=nil since it is the final servicer
143
+ # in the request/middleware chain.
144
+ #
145
+ # @return [::Sinatra::Base] the current controller as routed by Rack.
146
+ def app_instance
147
+ return unless defined?(::Sinatra) && defined?(::Sinatra::Base)
148
+
149
+ @_app_instance ||= begin
150
+ sinatra_layers = ObjectSpace.each_object(::Sinatra::Base).to_a
151
+ sinatra_layers.find { |layer| layer.app.nil? }
152
+ end
93
153
  end
94
154
  end
95
155
  end
@@ -41,9 +41,6 @@ module Contrast
41
41
  hash[conversion.key] = conversion.dot_path_array.join('.')
42
42
  end
43
43
  info('Set by environment', overrides: env_translations)
44
- rescue StandardError => e
45
- puts e
46
- sleep(5)
47
44
  end
48
45
 
49
46
  def application_libraries
@@ -37,28 +37,22 @@ module Contrast
37
37
  # Given new settings from TeamServer, update our logging to use the new
38
38
  # file and level, assuming they weren't set by local configuration.
39
39
  #
40
- # @param log_file [String] the file to which to log
41
- # @param log_level [String] the level at which to log
40
+ # @param log_file [String] the file to which to log, as provided by TeamServer settings
41
+ # @param log_level [String] the level at which to log, as provided by TeamServer settings
42
42
  def update log_file = nil, log_level = nil
43
- config = CONFIG.root.agent.logger
44
-
45
- config_path = config.path&.length.to_i.positive? ? config.path : nil
46
- config_level = config.level&.length&.positive? ? config.level : nil
47
-
48
- # config > settings > default
49
- path = valid_path(config_path || log_file)
50
- level_const = valid_level(config_level || log_level)
43
+ current_path = find_valid_path(log_file)
44
+ current_level_const = find_valid_level(log_level)
51
45
 
52
- path_change = path != previous_path
53
- level_change = level_const != previous_level
46
+ path_change = current_path != previous_path
47
+ level_change = current_level_const != previous_level
54
48
 
55
49
  # don't needlessly recreate logger
56
50
  return if @_logger && !(path_change || level_change)
57
51
 
58
- @previous_path = path
59
- @previous_level = level_const
52
+ @previous_path = current_path
53
+ @previous_level = current_level_const
60
54
 
61
- @_logger = build(path: path, level_const: level_const)
55
+ @_logger = build(path: current_path, level_const: current_level_const)
62
56
  # If we're logging to a new path, then let's start it w/ our helpful
63
57
  # data gathering messages
64
58
  log_update if path_change
@@ -108,6 +102,17 @@ module Contrast
108
102
  logger.extend(Contrast::Logger::Time)
109
103
  end
110
104
 
105
+ # Determine the valid path to which to log, given the precedence of config > settings > default.
106
+ #
107
+ # @param log_file [String, nil] the file to which to log as provided by the settings retrieved from the
108
+ # TeamServer.
109
+ # @return [String] the path to which to log or STDOUT / STDERR if one of those values provided.
110
+ def find_valid_path log_file
111
+ config = CONFIG.root.agent.logger
112
+ config_path = config.path&.length.to_i.positive? ? config.path : nil
113
+ valid_path(config_path || log_file)
114
+ end
115
+
111
116
  def valid_path path
112
117
  path = path.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : path
113
118
  return path if path == STDOUT_STR
@@ -129,6 +134,17 @@ module Contrast
129
134
  end
130
135
  end
131
136
 
137
+ # Determine the valid level to which to log, given the precedence of config > settings > default.
138
+ #
139
+ # @param log_level [String, nil] the level at which to log as provided by the settings retrieved from the
140
+ # TeamServer.
141
+ # @return [::Ougai::Logging::Severity] the level at which to log
142
+ def find_valid_level log_level
143
+ config = CONFIG.root.agent.logger
144
+ config_level = config.level&.length&.positive? ? config.level : nil
145
+ valid_level(config_level || log_level)
146
+ end
147
+
132
148
  def valid_level level
133
149
  level ||= DEFAULT_LEVEL
134
150
  level = level.upcase
@@ -52,7 +52,7 @@ module Contrast
52
52
  # Return a String representing the object invoking this method in the
53
53
  # form expected by our dataflow events.
54
54
  #
55
- # @param object [Object] the entity to convert to a String
55
+ # @param object [Object, nil] the entity to convert to a String
56
56
  # @return [String] the human readable form of the String, as defined by
57
57
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
58
58
  def to_contrast_string object
@@ -63,6 +63,8 @@ module Contrast
63
63
  return cached if cached
64
64
 
65
65
  object.dup
66
+ elsif object.nil?
67
+ Contrast::Utils::ObjectShare::NIL_STRING
66
68
  elsif object.cs__is_a?(Symbol)
67
69
  ":#{ object }"
68
70
  elsif object.cs__is_a?(Numeric)
@@ -4,7 +4,7 @@
4
4
  module Contrast
5
5
  module Utils
6
6
  # Utility methods for identifying instances that can be used interchangeably
7
- class DuckUtils
7
+ module DuckUtils
8
8
  class << self
9
9
  # Determine if the given object, or the object to which it delegates,
10
10
  # responds to the given method.
@@ -2,12 +2,13 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'objspace'
5
+ require 'singleton'
5
6
  require 'contrast/components/interface'
6
7
 
7
8
  module Contrast
8
9
  module Utils
9
10
  # Implementation of a heap dump util to automate generation
10
- class HeapDumpUtil
11
+ class HeapDumpUtil < Contrast::Agent::WorkerThread
11
12
  include Contrast::Components::Interface
12
13
  access_component :heap_dump, :logging
13
14
 
@@ -15,98 +16,113 @@ module Contrast
15
16
  FILE_WRITE_FLAGS = 'w'
16
17
 
17
18
  class << self
18
- def run
19
- return unless heap_dump_enabled?
20
-
21
- log_enabled_warning
22
- dir = heap_dump_control[:path]
23
- Dir.mkdir(dir) unless Dir.exist?(dir)
24
- return unless File.writable?(dir)
25
-
26
- delay = heap_dump_control[:delay]
27
- Contrast::Agent::Thread.new do
28
- logger.info("HEAP DUMP THREAD INITIALIZED. WAITING #{ delay } SECONDS TO BEGIN.")
29
- sleep(delay)
30
- capture_heap_dump
31
- end
32
- rescue StandardError => e
33
- logger.info(LOG_ERROR_DUMPS, e)
34
- nil
19
+ def enabled?
20
+ heap_dump_enabled?
21
+ end
22
+
23
+ def control
24
+ heap_dump_control
35
25
  end
26
+ end
27
+
28
+ def start_thread!
29
+ return unless Contrast::Utils::HeapDumpUtil.enabled?
36
30
 
37
- def log_enabled_warning
38
- dir = heap_dump_control[:path]
39
- window = heap_dump_control[:window]
40
- count = heap_dump_control[:count]
41
- delay = heap_dump_control[:delay]
42
- clean = heap_dump_control[:clean]
43
-
44
- logger.info <<~WARNING
45
- *****************************************************
46
- ******** HEAP DUMP HAS BEEN ENABLED ********
47
- *** APPLICATION PROCESS WILL EXIT UPON COMPLETION ***
48
- *****************************************************
49
-
50
- Heap dump is a debugging tool that snapshots the entire
51
- state of the Ruby VM. It is an exceptionally expensive
52
- process, and should only be used to debug especially
53
- pernicious errors.
54
-
55
- It will write multiple memory snaphots, which are liable
56
- to be multiple gigabytes in size.
57
- They will be named "[unix timestamp]-heap.dump",
58
- e.g.: 1020304050-heap.dump
59
-
60
- It will then call Ruby `exit()`.
61
-
62
- If this is not your specific intent, you can (and should)
63
- disable this option in your Contrast config file.
64
-
65
- HEAP DUMP PARAMETERS:
66
- \t[write files to this directory] dir: #{ dir }
67
- \t[wait this many seconds in between dumps] window: #{ window }
68
- \t[heap dump this many times] count: #{ count }
69
- \t[wait this many seconds into app lifetime] delay: #{ delay }
70
- \t[perform gc pass before dump] clean: #{ clean }
71
-
72
- *****************************************************
73
- ******** YOU HAVE BEEN WARNED ********
74
- *****************************************************
75
- WARNING
31
+ control = Contrast::Utils::HeapDumpUtil.control
32
+ log_enabled_warning
33
+ dir = control[:path]
34
+ Dir.mkdir(dir) unless Dir.exist?(dir)
35
+ return unless File.writable?(dir)
36
+
37
+ delay = control[:delay]
38
+ @_thread = Contrast::Agent::Thread.new do
39
+ logger.info("HEAP DUMP THREAD INITIALIZED. WAITING #{ delay } SECONDS TO BEGIN.")
40
+ sleep(delay)
41
+ capture_heap_dump
76
42
  end
43
+ rescue StandardError => e
44
+ logger.info(LOG_ERROR_DUMPS, e)
45
+ nil
46
+ end
47
+
48
+ def log_enabled_warning
49
+ control = Contrast::Utils::HeapDumpUtil.control
50
+ dir = control[:path]
51
+ window = control[:window]
52
+ count = control[:count]
53
+ delay = control[:delay]
54
+ clean = control[:clean]
55
+
56
+ logger.info <<~WARNING
57
+ *****************************************************
58
+ ******** HEAP DUMP HAS BEEN ENABLED ********
59
+ *** APPLICATION PROCESS WILL EXIT UPON COMPLETION ***
60
+ *****************************************************
61
+
62
+ Heap dump is a debugging tool that snapshots the entire
63
+ state of the Ruby VM. It is an exceptionally expensive
64
+ process, and should only be used to debug especially
65
+ pernicious errors.
66
+
67
+ It will write multiple memory snaphots, which are liable
68
+ to be multiple gigabytes in size.
69
+ They will be named "[unix timestamp]-heap.dump",
70
+ e.g.: 1020304050-heap.dump
71
+
72
+ It will then call Ruby `exit()`.
73
+
74
+ If this is not your specific intent, you can (and should)
75
+ disable this option in your Contrast config file.
76
+
77
+ HEAP DUMP PARAMETERS:
78
+ \t[write files to this directory] dir: #{ dir }
79
+ \t[wait this many seconds in between dumps] window: #{ window }
80
+ \t[heap dump this many times] count: #{ count }
81
+ \t[wait this many seconds into app lifetime] delay: #{ delay }
82
+ \t[perform gc pass before dump] clean: #{ clean }
83
+
84
+ *****************************************************
85
+ ******** YOU HAVE BEEN WARNED ********
86
+ *****************************************************
87
+ WARNING
88
+ end
89
+
90
+ def capture_heap_dump
91
+ control = Contrast::Utils::HeapDumpUtil.control
92
+ dir = control[:path]
93
+ window = control[:window]
94
+ count = control[:count]
95
+ clean = control[:clean]
96
+ logger.info('HEAP DUMP MAIN LOOP')
97
+ ObjectSpace.trace_object_allocations_start
98
+ count.times do |i|
99
+ logger.info('STARTING HEAP DUMP PASS', current_pass: i, max: count)
100
+ snapshot_heap(dir, clean)
101
+ logger.info('FINISHING HEAP DUMP PASS', current_pass: i, max: count)
102
+ sleep(window)
103
+ end
104
+ ensure
105
+ ObjectSpace.trace_object_allocations_stop
106
+ logger.info('*****************************************************')
107
+ logger.info('******** HEAP DUMP HAS CONCLUDED ********')
108
+ logger.info('*** APPLICATION PROCESS WILL EXIT SHORTLY ***')
109
+ logger.info('*****************************************************')
110
+ exit # rubocop:disable Rails/Exit We weren't kidding!
111
+ end
77
112
 
78
- def capture_heap_dump
79
- dir = heap_dump_control[:path]
80
- window = heap_dump_control[:window]
81
- count = heap_dump_control[:count]
82
- clean = heap_dump_control[:clean]
83
- logger.info('HEAP DUMP MAIN LOOP')
84
- ObjectSpace.trace_object_allocations_start
85
- count.times do |i|
86
- logger.info('STARTING HEAP DUMP PASS', current_pass: i + 1, max: count)
87
- output = "#{ Time.now.to_f }-heap.dump"
88
- output = File.join(dir, output)
89
- begin
90
- logger.info('OPENING HEADUMP FILE', dir: dir, file: output)
91
- file = File.new(output, FILE_WRITE_FLAGS)
92
- if clean
93
- logger.info('PERFORMING GARBAGE COLLECTION BEFORE HEAP DUMP')
94
- GC.start
95
- end
96
- ObjectSpace.dump_all(output: file)
97
- logger.info('FINISHING HEAP DUMP PASS', current_pass: i + 1, max: count)
98
- ensure
99
- file.close
100
- end
101
- sleep(window)
113
+ def snapshot_heap dir, clean
114
+ output = "#{ Time.now.to_f }-heap.dump"
115
+ output = File.join(dir, output)
116
+ begin
117
+ logger.info('OPENING HEADUMP FILE', dir: dir, file: output)
118
+ file = File.new(output, FILE_WRITE_FLAGS)
119
+ if clean
120
+ logger.info('PERFORMING GARBAGE COLLECTION BEFORE HEAP DUMP')
121
+ GC.start
102
122
  end
123
+ ObjectSpace.dump_all(output: file)
103
124
  ensure
104
- ObjectSpace.trace_object_allocations_stop
105
- logger.info('*****************************************************')
106
- logger.info('******** HEAP DUMP HAS CONCLUDED ********')
107
- logger.info('*** APPLICATION PROCESS WILL EXIT SHORTLY ***')
108
- logger.info('*****************************************************')
109
- exit # We weren't kidding!
125
+ file.close
110
126
  end
111
127
  end
112
128
  end