google-cloud-debugger 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/LICENSE +201 -0
  4. data/README.md +56 -0
  5. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  6. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  7. data/ext/google/cloud/debugger/debugger_c/evaluator.c +78 -0
  8. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  9. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  10. data/ext/google/cloud/debugger/debugger_c/tracer.c +478 -0
  11. data/ext/google/cloud/debugger/debugger_c/tracer.h +31 -0
  12. data/lib/google-cloud-debugger.rb +121 -0
  13. data/lib/google/cloud/debugger.rb +379 -0
  14. data/lib/google/cloud/debugger/agent.rb +204 -0
  15. data/lib/google/cloud/debugger/async_actor.rb +290 -0
  16. data/lib/google/cloud/debugger/breakpoint.rb +382 -0
  17. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1113 -0
  18. data/lib/google/cloud/debugger/breakpoint/source_location.rb +75 -0
  19. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  20. data/lib/google/cloud/debugger/breakpoint/variable.rb +304 -0
  21. data/lib/google/cloud/debugger/breakpoint_manager.rb +217 -0
  22. data/lib/google/cloud/debugger/credentials.rb +41 -0
  23. data/lib/google/cloud/debugger/debuggee.rb +204 -0
  24. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +78 -0
  25. data/lib/google/cloud/debugger/middleware.rb +77 -0
  26. data/lib/google/cloud/debugger/project.rb +135 -0
  27. data/lib/google/cloud/debugger/rails.rb +141 -0
  28. data/lib/google/cloud/debugger/service.rb +130 -0
  29. data/lib/google/cloud/debugger/tracer.rb +165 -0
  30. data/lib/google/cloud/debugger/transmitter.rb +129 -0
  31. data/lib/google/cloud/debugger/v2.rb +15 -0
  32. data/lib/google/cloud/debugger/v2/controller2_client.rb +299 -0
  33. data/lib/google/cloud/debugger/v2/controller2_client_config.json +43 -0
  34. data/lib/google/cloud/debugger/v2/debugger2_client.rb +378 -0
  35. data/lib/google/cloud/debugger/v2/debugger2_client_config.json +53 -0
  36. data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/data.rb +441 -0
  37. data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/debugger.rb +151 -0
  38. data/lib/google/cloud/debugger/v2/doc/google/devtools/source/v1/source_context.rb +161 -0
  39. data/lib/google/cloud/debugger/v2/doc/google/protobuf/timestamp.rb +81 -0
  40. data/lib/google/cloud/debugger/version.rb +22 -0
  41. data/lib/google/devtools/clouddebugger/v2/controller_pb.rb +47 -0
  42. data/lib/google/devtools/clouddebugger/v2/controller_services_pb.rb +97 -0
  43. data/lib/google/devtools/clouddebugger/v2/data_pb.rb +105 -0
  44. data/lib/google/devtools/clouddebugger/v2/debugger_pb.rb +74 -0
  45. data/lib/google/devtools/clouddebugger/v2/debugger_services_pb.rb +64 -0
  46. data/lib/google/devtools/source/v1/source_context_pb.rb +89 -0
  47. metadata +300 -0
@@ -0,0 +1,204 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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/async_actor"
17
+ require "google/cloud/debugger/breakpoint_manager"
18
+ require "google/cloud/debugger/debuggee"
19
+ require "google/cloud/debugger/debugger_c"
20
+ require "google/cloud/debugger/tracer"
21
+ require "google/cloud/debugger/transmitter"
22
+
23
+ module Google
24
+ module Cloud
25
+ module Debugger
26
+ ##
27
+ # # Agent
28
+ #
29
+ # The Stackdriver Debugger Agent runs on the same system where a debuggee
30
+ # application is running. The agent is responsible for sending state data,
31
+ # such as the value of program variables and the call stack, to
32
+ # Stackdriver Debugger when the code at a breakpoint location is executed.
33
+ #
34
+ # The Debugger Agent runs in its own child thread when started. It ensures
35
+ # the instrumented application is registered properly and constantly
36
+ # monitors for any active breakpoints. Once the agent gets updated with
37
+ # active breakpoints from Stackdriver Debugger service, it facilitates
38
+ # the breakpoints in application requests thread, then transport the
39
+ # result snapshot back to Stackdriver Debugger service asynchronously.
40
+ #
41
+ # @example
42
+ # require "google/cloud/debugger"
43
+ #
44
+ # debugger = Google::Cloud::Debugger.new
45
+ # agent = debugger.agent
46
+ # agent.start
47
+ #
48
+ class Agent
49
+ ##
50
+ # @private Debugger Agent is an asynchronous actor
51
+ include AsyncActor
52
+
53
+ ##
54
+ # @private The gRPC Service object.
55
+ attr_reader :service
56
+
57
+ ##
58
+ # The gRPC Debuggee representation of the debuggee application. It
59
+ # contains identification information to match running application to
60
+ # specific Cloud Source Repository code base, and correctly group
61
+ # same versions of the debuggee application together through a generated
62
+ # unique identifier.
63
+ # @return [Google::Cloud::Debugger::Debuggee]
64
+ attr_reader :debuggee
65
+
66
+ ##
67
+ # It manages syncing breakpoints between the Debugger Agent and
68
+ # Stackdriver Debugger service
69
+ # @return [Google::Cloud::Debugger::BreakpointManager]
70
+ attr_reader :breakpoint_manager
71
+
72
+ ##
73
+ # It monitors the debuggee application and triggers breakpoint
74
+ # evaluation when breakpoints are set.
75
+ # @return [Google::Cloud::Debugger::Tracer]
76
+ attr_reader :tracer
77
+
78
+ ##
79
+ # It sends evaluated breakpoints snapshot back to Stackdriver Debugger
80
+ # Service.
81
+ # @return [Google::Cloud::Debugger::Transmiter]
82
+ attr_reader :transmitter
83
+
84
+ ##
85
+ # @private The last exception captured in the agent child thread
86
+ attr_reader :last_exception
87
+
88
+ ##
89
+ # Create a new Debugger Agent instance.
90
+ #
91
+ # @param [Google::Cloud::Debugger::Service] service The gRPC Service
92
+ # object
93
+ # @param [String] module_name Name for the debuggee application.
94
+ # Optional.
95
+ # @param [String] module_version Version identifier for the debuggee
96
+ # application. Optional.
97
+ #
98
+ def initialize service, module_name:, module_version:
99
+ super()
100
+
101
+ @service = service
102
+ @debuggee = Debuggee.new service, module_name: module_name,
103
+ module_version: module_version
104
+ @tracer = Debugger::Tracer.new self
105
+ @breakpoint_manager = BreakpointManager.new service
106
+ @breakpoint_manager.on_breakpoints_change =
107
+ method :breakpoints_change_callback
108
+
109
+ @transmitter = Transmitter.new service, self
110
+ end
111
+
112
+ ##
113
+ # Starts the Debugger Agent in a child thread, where debuggee
114
+ # application registration and breakpoints querying will take place.
115
+ # It also starts the transmitter in another child thread.
116
+ #
117
+ def start
118
+ transmitter.start
119
+ async_start
120
+ end
121
+
122
+ ##
123
+ # Stops and terminates the Debugger Agent. It also properly shuts down
124
+ # transmitter and tracer.
125
+ #
126
+ # Once Debugger Agent is stopped, it cannot be started again.
127
+ #
128
+ def stop
129
+ tracer.stop
130
+ transmitter.stop
131
+ async_stop
132
+ end
133
+
134
+ ##
135
+ # Stops the tracer regardless of whether any active breakpoints are
136
+ # present. Once the tracer stops monitoring the debuggee application,
137
+ # the application can return to normal performance.
138
+ def stop_tracer
139
+ tracer.stop
140
+ end
141
+
142
+ ##
143
+ # @private Callback function for AsyncActor module that kicks off
144
+ # the asynchronous job in a loop.
145
+ def run_backgrounder
146
+ sync_breakpoints = ensure_debuggee_registration
147
+
148
+ if sync_breakpoints
149
+ sync_result =
150
+ breakpoint_manager.sync_active_breakpoints debuggee.id
151
+ debuggee.revoke_registration unless sync_result
152
+ end
153
+ rescue => e
154
+ @last_exception = e
155
+ end
156
+
157
+ private
158
+
159
+ ##
160
+ # @private Callback function for breakpoint manager when any updates
161
+ # happens to the list of active breakpoints
162
+ def breakpoints_change_callback active_breakpoints
163
+ if active_breakpoints.empty?
164
+ tracer.stop
165
+ else
166
+ tracer.start
167
+ end
168
+ end
169
+
170
+ ##
171
+ # @private Helper method to register the debuggee application if not
172
+ # registered already.
173
+ def ensure_debuggee_registration
174
+ if debuggee.registered?
175
+ registration_result = true
176
+ else
177
+ registration_result = debuggee.register
178
+ if registration_result
179
+ puts "Debuggee #{debuggee.id} successfully registered"
180
+ end
181
+ end
182
+
183
+ registration_result
184
+ end
185
+
186
+ ##
187
+ # @private Override AsyncActor#async_stop to immediately kill the child
188
+ # thread instead of waiting for it to return, because the breakpoints
189
+ # are queried with a hanging long poll mechanism.
190
+ def async_stop
191
+ @startup_lock.synchronize do
192
+ unless @thread.nil?
193
+ tracer.stop
194
+
195
+ @async_state = :stopped
196
+ @thread.kill
197
+ @thread.join
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,290 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
+ require "set"
16
+
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ ##
22
+ # # AsyncActor
23
+ #
24
+ # @private An module that provides a class asynchronous capability when
25
+ # included. It can create a child thread to run background jobs, and
26
+ # help make sure the child thread terminates properly when process
27
+ # is interrupted.
28
+ #
29
+ # To use AsyncActor, the classes that are including this module need to
30
+ # define a run_backgrounder method that does the background job. The
31
+ # classes can then control the child thread job through instance methods
32
+ # like async_start, async_stop, etc.
33
+ #
34
+ # @example
35
+ # class Foo
36
+ # include AsyncActor
37
+ #
38
+ # def initialize
39
+ # super()
40
+ # end
41
+ #
42
+ # def run_backgrounder
43
+ # # run async job
44
+ # end
45
+ # end
46
+ #
47
+ # foo = Foo.new
48
+ # foo.async_start
49
+ #
50
+ module AsyncActor
51
+ include MonitorMixin
52
+
53
+ CLEANUP_TIMEOUT = 10.0
54
+ WAIT_INTERVAL = 1.0
55
+
56
+ @cleanup_list = nil
57
+ @exit_lock = Monitor.new
58
+
59
+ ##
60
+ # @private The async actor state
61
+ attr_reader :async_state
62
+
63
+ ##
64
+ # Starts the child thread and asynchronous job
65
+ def async_start
66
+ ensure_thread
67
+ end
68
+
69
+ ##
70
+ # Nicely ask the child thread to stop by setting the state to
71
+ # :stopping if it's not stopped already.
72
+ #
73
+ # @return [Boolean] False if child thread is already stopped. Otherwise
74
+ # true.
75
+ def async_stop
76
+ ensure_thread
77
+ synchronize do
78
+ if async_state != :stopped
79
+ @async_state = :stopping
80
+ @lock_cond.broadcast
81
+ true
82
+ else
83
+ false
84
+ end
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Set the state to :suspend if the current state is :running.
90
+ #
91
+ # @return [Boolean] Returns true if the state has been set to
92
+ # :suspended. Otherwise return false.
93
+ #
94
+ def async_suspend
95
+ ensure_thread
96
+ synchronize do
97
+ if async_state == :running
98
+ @async_state = :suspended
99
+ @lock_cond.broadcast
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Set the state to :running if the current state is :suspended.
109
+ #
110
+ # @return [Boolean] True if the state has been set to :running.
111
+ # Otherwise return false.
112
+ #
113
+ def async_resume
114
+ ensure_thread
115
+ synchronize do
116
+ if async_state == :suspended
117
+ @async_state = :running
118
+ @lock_cond.broadcast
119
+ true
120
+ else
121
+ false
122
+ end
123
+ end
124
+ end
125
+
126
+ ##
127
+ # Check if async job is running
128
+ #
129
+ # @return [Boolean] True if state equals :running. Otherwise false.
130
+ def async_running?
131
+ synchronize do
132
+ async_state == :running
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Check if async job is suspended
138
+ #
139
+ # @return [Boolean] True if state equals :suspended. Otherwise false.
140
+ def async_suspended?
141
+ synchronize do
142
+ async_state == :suspended
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Check if async job is working.
148
+ #
149
+ # @return [Boolean] True if state is either :running or :suspended.
150
+ # Otherwise false.
151
+ def async_working?
152
+ synchronize do
153
+ async_state == :suspended || async_state == :running
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Check if async job has stopped
159
+ #
160
+ # @return [Boolean] True if state equals :stopped. Otherwise false.
161
+ def async_stopped?
162
+ synchronize do
163
+ async_state == :stopped
164
+ end
165
+ end
166
+
167
+ ##
168
+ # Ask async job to stop. Then forcefully kill thread if it doesn't stop
169
+ # after timeout if needed.
170
+ #
171
+ # @param [Boolean] force If true, forcefully kill thread after timeout.
172
+ # Default to false.
173
+ #
174
+ # @return [Symbol] :stopped if async job already stopped. :waited if
175
+ # async job terminates within timeout range. :timeout if async job
176
+ # doesn't terminate after timeout. :forced if thread is killed by
177
+ # force after timeout.
178
+ def async_stop! timeout, force: false
179
+ return :stopped unless async_stop
180
+ return :waited if wait_until_async_stopped timeout
181
+ return :timeout unless force
182
+ @thread.kill
183
+ @thread.join
184
+ :forced
185
+ end
186
+
187
+ ##
188
+ # @private Cleanup this async job when process exists
189
+ #
190
+ def self.register_for_cleanup actor
191
+ @exit_lock.synchronize do
192
+ unless @cleanup_list
193
+ @cleanup_list = []
194
+ at_exit { run_cleanup }
195
+ end
196
+ @cleanup_list.push actor
197
+ end
198
+ end
199
+
200
+ ##
201
+ # @private Take this async job off exit cleanup list
202
+ #
203
+ def self.unregister_for_cleanup actor
204
+ @exit_lock.synchronize do
205
+ @cleanup_list.delete actor if @cleanup_list
206
+ end
207
+ end
208
+
209
+ ##
210
+ # @private Cleanup the async job
211
+ #
212
+ def self.run_cleanup
213
+ @exit_lock.synchronize do
214
+ if @cleanup_list
215
+ until @cleanup_list.empty?
216
+ @cleanup_list.shift.async_stop! CLEANUP_TIMEOUT, force: true
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ private_class_method :run_cleanup
223
+
224
+ private
225
+
226
+ ##
227
+ # @private Helper method to async_stop! to wait for async job to
228
+ # terminate.
229
+ #
230
+ # @return [Boolean] True if async job terminated. False if timeout.
231
+ #
232
+ def wait_until_async_stopped timeout = nil
233
+ ensure_thread
234
+ deadline = timeout ? ::Time.new.to_f + timeout : nil
235
+ synchronize do
236
+ until async_state == :stopped
237
+ cur_time = ::Time.new.to_f
238
+ return false if deadline && cur_time >= deadline
239
+ interval = deadline ? deadline - cur_time : WAIT_INTERVAL
240
+ interval = WAIT_INTERVAL if interval > WAIT_INTERVAL
241
+ @lock_cond.wait interval
242
+ end
243
+ end
244
+ true
245
+ end
246
+
247
+ ##
248
+ # @private Constructor to initialize MonitorMixin
249
+ #
250
+ def initialize
251
+ super()
252
+ @startup_lock = Mutex.new
253
+ end
254
+
255
+ ##
256
+ # @private Wrapper method for running the async job. It requires classes
257
+ # that include AsyncActor module to define a run_backgrounder method.
258
+ # Then it runs a loop that checks for the state is workable (:running
259
+ # or :suspended), which calls the run_backgrounder method. It ensures
260
+ # the state variable gets set to :stopped when this method exists.
261
+ def async_run_job
262
+ fail "run_backgrounder method not defined" unless
263
+ respond_to? :run_backgrounder
264
+ run_backgrounder while async_working?
265
+ ensure
266
+ @async_state = :stopped
267
+ end
268
+
269
+ ##
270
+ # @private Ensures the child thread is started and kick off the job
271
+ # to run async_run_job. Also make calls register_for_cleanup on the
272
+ # async job to make sure it exits properly.
273
+ def ensure_thread
274
+ fail "async_actor not initialized" if @startup_lock.nil?
275
+ @startup_lock.synchronize do
276
+ if (@thread.nil? || !@thread.alive?) && @async_state != :stopped
277
+ @lock_cond = new_cond
278
+ AsyncActor.register_for_cleanup self
279
+ @thread = Thread.new do
280
+ async_run_job
281
+ AsyncActor.unregister_for_cleanup self
282
+ end
283
+ @async_state = :running
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end