google-cloud-debugger 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +233 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/INSTRUMENTATION.md +115 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +266 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  13. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  14. data/ext/google/cloud/debugger/debugger_c/evaluator.c +115 -0
  15. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  16. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  17. data/ext/google/cloud/debugger/debugger_c/tracer.c +542 -0
  18. data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
  19. data/lib/google-cloud-debugger.rb +181 -0
  20. data/lib/google/cloud/debugger.rb +259 -0
  21. data/lib/google/cloud/debugger/agent.rb +255 -0
  22. data/lib/google/cloud/debugger/backoff.rb +70 -0
  23. data/lib/google/cloud/debugger/breakpoint.rb +443 -0
  24. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
  25. data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
  26. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  27. data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
  28. data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
  29. data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
  30. data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
  31. data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
  32. data/lib/google/cloud/debugger/credentials.rb +50 -0
  33. data/lib/google/cloud/debugger/debuggee.rb +222 -0
  34. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
  35. data/lib/google/cloud/debugger/logpoint.rb +98 -0
  36. data/lib/google/cloud/debugger/middleware.rb +200 -0
  37. data/lib/google/cloud/debugger/project.rb +110 -0
  38. data/lib/google/cloud/debugger/rails.rb +174 -0
  39. data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
  40. data/lib/google/cloud/debugger/service.rb +88 -0
  41. data/lib/google/cloud/debugger/snappoint.rb +208 -0
  42. data/lib/google/cloud/debugger/tracer.rb +137 -0
  43. data/lib/google/cloud/debugger/transmitter.rb +199 -0
  44. data/lib/google/cloud/debugger/version.rb +22 -0
  45. metadata +353 -0
@@ -0,0 +1,222 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "digest/sha1"
17
+ require "google/cloud/debugger/backoff"
18
+ require "google/cloud/debugger/debuggee/app_uniquifier_generator"
19
+ require "google/cloud/debugger/version"
20
+ require "google/cloud/env"
21
+ require "json"
22
+
23
+ module Google
24
+ module Cloud
25
+ module Debugger
26
+ ##
27
+ # # Debuggee
28
+ #
29
+ # Represent a debuggee application. Contains information that identifies
30
+ # debuggee applications from each other. Maps to gRPC struct
31
+ # Google::Cloud::Debugger::V2::Debuggee.
32
+ #
33
+ # It also automatically loads source context information generated from
34
+ # Cloud SDK. See [Stackdriver Debugger
35
+ # doc](https://cloud.google.com/debugger/docs/source-context#app_engine_standard_python)
36
+ # for more information on how to generate this source context information
37
+ # when used on Google Container Engine and Google Compute engine. This
38
+ # step is taken care of if debuggee application is hosted on Google App
39
+ # Engine Flexibile.
40
+ #
41
+ # To ensure the multiple instances of the application are indeed the same
42
+ # application, the debuggee also compute a "uniquifier" generated from
43
+ # source context or application source code.
44
+ #
45
+ class Debuggee
46
+ ##
47
+ # @private The gRPC Service object.
48
+ attr_reader :service
49
+
50
+ ##
51
+ # Name for the debuggee application
52
+ # @return [String]
53
+ attr_reader :service_name
54
+
55
+ ##
56
+ # Version identifier for the debuggee application
57
+ # @return [String]
58
+ attr_reader :service_version
59
+
60
+ ##
61
+ # Registered Debuggee ID. Set by Stackdriver Debugger service when
62
+ # a debuggee application is sucessfully registered.
63
+ # @return [String]
64
+ attr_reader :id
65
+
66
+ ##
67
+ # @private Construct a new instance of Debuggee
68
+ def initialize service, service_name:, service_version:
69
+ @service = service
70
+ @service_name = service_name
71
+ @service_version = service_version
72
+ @env = Google::Cloud.env
73
+ @computed_uniquifier = nil
74
+ @id = nil
75
+ @register_backoff = Google::Cloud::Debugger::Backoff.new
76
+ end
77
+
78
+ ##
79
+ # Register the current application as a debuggee with Stackdriver
80
+ # Debuggee service.
81
+ # @return [Boolean] True if registered sucessfully; otherwise false.
82
+ def register
83
+ # Wait if backoff applies
84
+ sleep @register_backoff.interval if @register_backoff.backing_off?
85
+
86
+ begin
87
+ response = service.register_debuggee to_grpc
88
+ @id = response.debuggee.id
89
+ rescue StandardError
90
+ revoke_registration
91
+ end
92
+
93
+ registered = registered?
94
+ registered ? @register_backoff.succeeded : @register_backoff.failed
95
+
96
+ registered
97
+ end
98
+
99
+ ##
100
+ # Check whether this debuggee is currently registered or not
101
+ # @return [Boolean] True if debuggee is registered; otherwise false.
102
+ def registered?
103
+ !id.nil?
104
+ end
105
+
106
+ ##
107
+ # Revoke the registration of this debuggee
108
+ def revoke_registration
109
+ @id = nil
110
+ end
111
+
112
+ ##
113
+ # Convert this debuggee into a gRPC
114
+ # Google::Cloud::Debugger::V2::Debuggee struct.
115
+ def to_grpc
116
+ source_context = source_context_from_json_file "source-context.json"
117
+ Google::Cloud::Debugger::V2::Debuggee.new(
118
+ id: id.to_s,
119
+ project: project_id_for_request_arg.to_s,
120
+ description: description.to_s,
121
+ labels: labels,
122
+ agent_version: agent_version.to_s
123
+ ).tap do |d|
124
+ if source_context
125
+ d.source_contexts << source_context
126
+ d.ext_source_contexts << ext_source_context_grpc(source_context)
127
+ end
128
+ d.uniquifier = compute_uniquifier d
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def source_context_from_json_file file_path
135
+ json = File.read file_path
136
+ Devtools::Source::V1::SourceContext.decode_json json
137
+ rescue StandardError
138
+ nil
139
+ end
140
+
141
+ def ext_source_context_grpc source_context
142
+ Devtools::Source::V1::SourceContext.new context: source_context
143
+ end
144
+
145
+ ##
146
+ # @private Build labels hash for this debuggee
147
+ def labels
148
+ {
149
+ "projectid" => String(project_id),
150
+ "module" => String(service_name),
151
+ "version" => String(service_version)
152
+ }
153
+ end
154
+
155
+ ##
156
+ # @private Build description string for this debuggee. In
157
+ # "<module name> - <module version>" format. Or just the module
158
+ # version if module name is missing.
159
+ #
160
+ # @return [String] A compact debuggee description.
161
+ #
162
+ def description
163
+ if service_version.nil? || service_version.empty?
164
+ service_name
165
+ else
166
+ "#{service_name} - #{service_version}"
167
+ end
168
+ end
169
+
170
+ ##
171
+ # @private Get debuggee project id
172
+ def project_id
173
+ service.project
174
+ end
175
+
176
+ ##
177
+ # @private
178
+ # Get project to send as a debuggee argument. This is the numeric
179
+ # project ID if available (and if it matches the project set in the
180
+ # configuration). Otherwise, use the configured project.
181
+ def project_id_for_request_arg
182
+ if project_id == @env.lookup_metadata("project", "project-id")
183
+ numeric_id = @env.numeric_project_id
184
+ return numeric_id.to_s if numeric_id
185
+ end
186
+ project_id
187
+ end
188
+
189
+ ##
190
+ # @private Build debuggee agent version identifier
191
+ def agent_version
192
+ "google.com/ruby#{RUBY_VERSION}-#{Google::Cloud::Debugger::VERSION}"
193
+ end
194
+
195
+ ##
196
+ # @private Generate a debuggee uniquifier from source context
197
+ # information or application source code
198
+ def compute_uniquifier partial_debuggee
199
+ return @computed_uniquifier if @computed_uniquifier
200
+
201
+ sha = Digest::SHA1.new
202
+ sha << partial_debuggee.to_s
203
+
204
+ if partial_debuggee.source_contexts.empty? &&
205
+ partial_debuggee.ext_source_contexts.empty?
206
+ AppUniquifierGenerator.generate_app_uniquifier sha
207
+ end
208
+
209
+ @computed_uniquifier = sha.hexdigest
210
+ end
211
+
212
+ ##
213
+ # @private Helper method to parse json file
214
+ def read_app_json_file file_path
215
+ JSON.parse File.read(file_path), symbolize_names: true
216
+ rescue StandardError
217
+ nil
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,76 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "pathname"
17
+ require "set"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Debugger
22
+ class Debuggee
23
+ ##
24
+ # Generates an application debuggee uniquifier by hashing the
25
+ # application file stats.
26
+ #
27
+ module AppUniquifierGenerator
28
+ ##
29
+ # Max number of directory levels the generator looks into.
30
+ MAX_DEPTH = 10
31
+
32
+ ##
33
+ # Computes the application uniquifier by examine the file stats of
34
+ # the files in the given application root directory. It only looks at
35
+ # .rb files and Gemfile.lock
36
+ #
37
+ # @param [Digest::SHA] sha A digest SHA object used to add the hashing
38
+ # values
39
+ # @param [String] app_path Application root directory where the Ruby
40
+ # application is located.
41
+ #
42
+ # @return [NilClass]
43
+ #
44
+ def self.generate_app_uniquifier sha, app_path = nil
45
+ app_path ||= if defined?(::Rack::Directory)
46
+ Rack::Directory.new("").root
47
+ else
48
+ Dir.getwd
49
+ end
50
+
51
+ process_directory sha, app_path
52
+
53
+ nil
54
+ end
55
+
56
+ ##
57
+ # @private Recursively parse files and directories.
58
+ def self.process_directory sha, path, depth = 1
59
+ return if depth > MAX_DEPTH
60
+
61
+ pathname = Pathname.new path
62
+ pathname.each_child do |entry|
63
+ if entry.directory?
64
+ process_directory sha, entry, depth + 1
65
+ else
66
+ next unless entry.extname == ".rb" ||
67
+ entry.basename.to_s == "Gemfile.lock"
68
+ sha << "#{entry.expand_path}:#{entry.stat.size}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,98 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/debugger/breakpoint"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ ##
22
+ # # Logpoint
23
+ #
24
+ # A kind of {Google::Cloud::Debugger::Breakpoint} that can be evaluated
25
+ # to generate a formatted log string, which later can be submitted to
26
+ # Stackdriver Logging service
27
+ #
28
+ class Logpoint < Breakpoint
29
+ ##
30
+ # Evaluate the breakpoint unless it's already marked as completed.
31
+ # Store evaluted expressions and stack frame variables in
32
+ # `@evaluated_expressions` and `@evaluated_log_message`.
33
+ #
34
+ # @param [Array<Binding>] call_stack_bindings An array of Ruby Binding
35
+ # objects, from the call stack that leads to the triggering of the
36
+ # breakpoints.
37
+ #
38
+ # @return [Boolean] True if evaluated successfully; false otherwise.
39
+ #
40
+ def evaluate call_stack_bindings
41
+ synchronize do
42
+ binding = call_stack_bindings[0]
43
+
44
+ return false if complete? || !check_condition(binding)
45
+
46
+ begin
47
+ evaluate_log_message binding
48
+ rescue StandardError
49
+ return false
50
+ end
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ ##
57
+ # @private Evaluate the expressions and log message. Store the result
58
+ # in @evaluated_log_message
59
+ def evaluate_log_message binding
60
+ evaluated_expressions = expressions.map do |expression|
61
+ Evaluator.readonly_eval_expression binding, expression
62
+ end
63
+
64
+ @evaluated_log_message =
65
+ format_message log_message_format, evaluated_expressions
66
+ end
67
+
68
+ ##
69
+ # @private Format log message by interpolate expressions.
70
+ #
71
+ # @example
72
+ # log_point = Google::Cloud::Debugger::Logpoint.new
73
+ # log_point.format_message(
74
+ # "Hello $0", ["World"]) #=> "Hello \"World\""
75
+ #
76
+ # @param [String] message_format The message with with
77
+ # expression placeholders such as `$0`, `$1`, etc.
78
+ # @param [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
79
+ # expressions An array of evaluated expression variables to be
80
+ # placed into message_format's placeholders. The variables need
81
+ # to have type equal String.
82
+ #
83
+ # @return [String] The formatted message string
84
+ #
85
+ def format_message message_format, expressions
86
+ # Substitute placeholders with expressions
87
+ message = message_format.gsub(/(?<!\$)\$\d+/) do |placeholder|
88
+ index = placeholder.match(/\$(\d+)/)[1].to_i
89
+ index < expressions.size ? expressions[index].inspect : ""
90
+ end
91
+
92
+ # Unescape "$" characters
93
+ message.gsub(/\$\$/, "$")
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,200 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "set"
17
+
18
+ require "google/cloud/logging/logger"
19
+ require "google/cloud/debugger/request_quota_manager"
20
+
21
+ module Google
22
+ module Cloud
23
+ module Debugger
24
+ ##
25
+ # Rack Middleware implementation that supports Stackdriver Debugger Agent
26
+ # in Rack-based Ruby frameworks. It instantiates a new debugger agent if
27
+ # one isn't given already. It helps optimize Debugger Agent Tracer
28
+ # performance by suspending and resuming the tracer between each request.
29
+ #
30
+ # To use this middleware, simply install it in your Rack configuration.
31
+ # The middleware will take care of registering itself with the
32
+ # Stackdriver Debugger and activating the debugger agent. The location
33
+ # of the middleware in the middleware stack matters: breakpoints will be
34
+ # detected in middleware appearing after but not before this middleware.
35
+ #
36
+ # For best results, you should also call {Middleware.start_agents}
37
+ # during application initialization. See its documentation for details.
38
+ #
39
+ class Middleware
40
+ ##
41
+ # Reset deferred start mechanism.
42
+ # @private
43
+ #
44
+ def self.reset_deferred_start
45
+ @debuggers_to_start = Set.new
46
+ end
47
+
48
+ reset_deferred_start
49
+
50
+ ##
51
+ # Start the debugger. Either adds it to the deferred list, or, if the
52
+ # deferred list has already been started, starts immediately.
53
+ #
54
+ # @param [Google::Cloud::Debugger::Project] debugger
55
+ #
56
+ # @private
57
+ #
58
+ def self.deferred_start debugger
59
+ if @debuggers_to_start
60
+ @debuggers_to_start << debugger
61
+ else
62
+ debugger.start
63
+ end
64
+ end
65
+
66
+ ##
67
+ # This should be called once the application determines that it is safe
68
+ # to start background threads and open gRPC connections. It informs
69
+ # the middleware system that it can start debugger agents.
70
+ #
71
+ # Generally, this matters if the application forks worker processes;
72
+ # this method should be called only after workers are forked, since
73
+ # threads and network connections interact badly with fork. For
74
+ # example, when running Puma in
75
+ # [clustered mode](https://github.com/puma/puma#clustered-mode), this
76
+ # method should be called in an `on_worker_boot` block.
77
+ #
78
+ # If the application does no forking, this method can be called any
79
+ # time early in the application initialization process.
80
+ #
81
+ # If {Middleware.start_agents} is never called, the debugger agent will
82
+ # be started when the first request is received. This should be safe,
83
+ # but it will probably mean breakpoints will not be recognized during
84
+ # that first request. For best results, an application should call this
85
+ # method at the appropriate time.
86
+ #
87
+ def self.start_agents
88
+ return unless @debuggers_to_start
89
+ @debuggers_to_start.each(&:start)
90
+ @debuggers_to_start = nil
91
+ end
92
+
93
+ ##
94
+ # Create a new Debugger Middleware.
95
+ #
96
+ # @param [Rack Application] app Rack application
97
+ # @param [Google::Cloud::Debugger::Project] debugger A debugger to be
98
+ # used by this middleware. If not given, will construct a new one
99
+ # using the other parameters.
100
+ # @param [Hash] kwargs Hash of configuration settings. Used for backward
101
+ # API compatibility. See the [Configuration
102
+ # Guide](https://googleapis.dev/ruby/stackdriver/latest/file.INSTRUMENTATION_CONFIGURATION.html)
103
+ # for the prefered way to set configuration parameters.
104
+ #
105
+ # @return [Google::Cloud::Debugger::Middleware] A new
106
+ # Google::Cloud::Debugger::Middleware instance
107
+ #
108
+ def initialize app, debugger: nil, **kwargs
109
+ @app = app
110
+
111
+ load_config kwargs
112
+
113
+ if debugger
114
+ @debugger = debugger
115
+ else
116
+ @debugger =
117
+ Debugger.new(project_id: configuration.project_id,
118
+ credentials: configuration.credentials,
119
+ service_name: configuration.service_name,
120
+ service_version: configuration.service_version)
121
+
122
+ @debugger.agent.quota_manager =
123
+ Google::Cloud::Debugger::RequestQuotaManager.new
124
+ end
125
+
126
+ Middleware.deferred_start @debugger
127
+ end
128
+
129
+ ##
130
+ # Rack middleware entry point. In most Rack based frameworks, a request
131
+ # is served by one thread. It enables/resume the debugger breakpoints
132
+ # tracing and stops/pauses the tracing afterwards to help improve
133
+ # debugger performance.
134
+ #
135
+ # @param [Hash] env Rack environment hash
136
+ #
137
+ # @return [Rack::Response] The response from downstream Rack app
138
+ #
139
+ def call env
140
+ # Ensure the agent is running. (Note the start method is idempotent.)
141
+ @debugger.start
142
+
143
+ # Enable/resume breakpoints tracing
144
+ @debugger.agent.tracer.start
145
+
146
+ # Use Stackdriver Logger for debugger if available
147
+ if env["rack.logger"].is_a? Google::Cloud::Logging::Logger
148
+ @debugger.agent.logger = env["rack.logger"]
149
+ end
150
+
151
+ @app.call env
152
+ ensure
153
+ # Stop breakpoints tracing beyond this point
154
+ @debugger.agent.tracer.disable_traces_for_thread
155
+
156
+ # Reset quotas after each request finishes.
157
+ @debugger.agent.quota_manager.reset if @debugger.agent.quota_manager
158
+ end
159
+
160
+ private
161
+
162
+ ##
163
+ # Consolidate configurations from various sources. Also set
164
+ # instrumentation config parameters to default values if not set
165
+ # already.
166
+ #
167
+ def load_config **kwargs
168
+ project_id = kwargs[:project] || kwargs[:project_id]
169
+ configuration.project_id = project_id unless project_id.nil?
170
+
171
+ creds = kwargs[:credentials] || kwargs[:keyfile]
172
+ configuration.credentials = creds unless creds.nil?
173
+
174
+ service_name = kwargs[:service_name]
175
+ configuration.service_name = service_name unless service_name.nil?
176
+
177
+ service_vers = kwargs[:service_version]
178
+ configuration.service_version = service_vers unless service_vers.nil?
179
+
180
+ init_default_config
181
+ end
182
+
183
+ ##
184
+ # Fallback to default configuration values if not defined already
185
+ def init_default_config
186
+ configuration.project_id ||= Debugger.default_project_id
187
+ configuration.credentials ||= Debugger.default_credentials
188
+ configuration.service_name ||= Debugger.default_service_name
189
+ configuration.service_version ||= Debugger.default_service_version
190
+ end
191
+
192
+ ##
193
+ # @private Get Google::Cloud::Debugger.configure
194
+ def configuration
195
+ Google::Cloud::Debugger.configure
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end