google-cloud-debugger 0.24.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.
- checksums.yaml +7 -0
- data/.yardopts +8 -0
- data/LICENSE +201 -0
- data/README.md +56 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.c +78 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
- data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.c +478 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.h +31 -0
- data/lib/google-cloud-debugger.rb +121 -0
- data/lib/google/cloud/debugger.rb +379 -0
- data/lib/google/cloud/debugger/agent.rb +204 -0
- data/lib/google/cloud/debugger/async_actor.rb +290 -0
- data/lib/google/cloud/debugger/breakpoint.rb +382 -0
- data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1113 -0
- data/lib/google/cloud/debugger/breakpoint/source_location.rb +75 -0
- data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
- data/lib/google/cloud/debugger/breakpoint/variable.rb +304 -0
- data/lib/google/cloud/debugger/breakpoint_manager.rb +217 -0
- data/lib/google/cloud/debugger/credentials.rb +41 -0
- data/lib/google/cloud/debugger/debuggee.rb +204 -0
- data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +78 -0
- data/lib/google/cloud/debugger/middleware.rb +77 -0
- data/lib/google/cloud/debugger/project.rb +135 -0
- data/lib/google/cloud/debugger/rails.rb +141 -0
- data/lib/google/cloud/debugger/service.rb +130 -0
- data/lib/google/cloud/debugger/tracer.rb +165 -0
- data/lib/google/cloud/debugger/transmitter.rb +129 -0
- data/lib/google/cloud/debugger/v2.rb +15 -0
- data/lib/google/cloud/debugger/v2/controller2_client.rb +299 -0
- data/lib/google/cloud/debugger/v2/controller2_client_config.json +43 -0
- data/lib/google/cloud/debugger/v2/debugger2_client.rb +378 -0
- data/lib/google/cloud/debugger/v2/debugger2_client_config.json +53 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/data.rb +441 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/debugger.rb +151 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/source/v1/source_context.rb +161 -0
- data/lib/google/cloud/debugger/v2/doc/google/protobuf/timestamp.rb +81 -0
- data/lib/google/cloud/debugger/version.rb +22 -0
- data/lib/google/devtools/clouddebugger/v2/controller_pb.rb +47 -0
- data/lib/google/devtools/clouddebugger/v2/controller_services_pb.rb +97 -0
- data/lib/google/devtools/clouddebugger/v2/data_pb.rb +105 -0
- data/lib/google/devtools/clouddebugger/v2/debugger_pb.rb +74 -0
- data/lib/google/devtools/clouddebugger/v2/debugger_services_pb.rb +64 -0
- data/lib/google/devtools/source/v1/source_context_pb.rb +89 -0
- 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
|