google-cloud-debugger 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
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